mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
126 Commits
sschroever
...
gdejong/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
561adcd0d6 | ||
|
|
05c5ba2677 | ||
|
|
efb9ea91b7 | ||
|
|
36ed176c4c | ||
|
|
aa21c57185 | ||
|
|
6e7f21f827 | ||
|
|
37580fa5ff | ||
|
|
b26ec206a0 | ||
|
|
354d98dbb7 | ||
|
|
6b99639f0e | ||
|
|
a5d4a3e87f | ||
|
|
c19a30cdb8 | ||
|
|
b61dc4e151 | ||
|
|
27a248def0 | ||
|
|
0e72a531e0 | ||
|
|
8842b11ad3 | ||
|
|
39550227a7 | ||
|
|
867985819a | ||
|
|
0a6c880e9c | ||
|
|
ae4a196fd6 | ||
|
|
b5ef39c89c | ||
|
|
21b6fc7bd1 | ||
|
|
cec781b317 | ||
|
|
76778344f6 | ||
|
|
c663c0cb7a | ||
|
|
0f7231a3ad | ||
|
|
4affdc8519 | ||
|
|
97d87cc9c0 | ||
|
|
4c9ab70bb6 | ||
|
|
03232ad9b9 | ||
|
|
4f48e05d8f | ||
|
|
57eb44285a | ||
|
|
4dd39610e7 | ||
|
|
643fd4e8a5 | ||
|
|
67c57c2442 | ||
|
|
2ec3938737 | ||
|
|
486ee7353f | ||
|
|
7cbe859b8c | ||
|
|
3cfcdda0fc | ||
|
|
952759209d | ||
|
|
9ad5f6e37c | ||
|
|
ebb6ff46b4 | ||
|
|
a305bf6da0 | ||
|
|
1d3cc10ff0 | ||
|
|
8d93549935 | ||
|
|
7096ce6075 | ||
|
|
9652c6f990 | ||
|
|
f02d9adcb2 | ||
|
|
9dda67e6cc | ||
|
|
51fdf18577 | ||
|
|
e3253011c3 | ||
|
|
d1836383ab | ||
|
|
882b256c18 | ||
|
|
36e0765734 | ||
|
|
d46a1ab87e | ||
|
|
3be79074d8 | ||
|
|
d8991627a7 | ||
|
|
1344321746 | ||
|
|
559a55b7d0 | ||
|
|
2e28ac9828 | ||
|
|
4d7b88a986 | ||
|
|
c0e177e4cd | ||
|
|
768b05bee6 | ||
|
|
048203434e | ||
|
|
9b89c0863c | ||
|
|
d03d536376 | ||
|
|
c812c95ea9 | ||
|
|
0d23dd1845 | ||
|
|
e29c08604d | ||
|
|
22c1f07017 | ||
|
|
9dd7621c18 | ||
|
|
3c59795c1b | ||
|
|
2688088750 | ||
|
|
a7b3378bda | ||
|
|
d7a10eda13 | ||
|
|
78e833ff85 | ||
|
|
edf2b7ca79 | ||
|
|
9ab79cb69b | ||
|
|
ac78cc709d | ||
|
|
2ef223ec9d | ||
|
|
2f797a322a | ||
|
|
3537e58305 | ||
|
|
f5e816d967 | ||
|
|
c0acb8b202 | ||
|
|
8f2183b6c2 | ||
|
|
278b679049 | ||
|
|
29657997f3 | ||
|
|
3562e4c764 | ||
|
|
d1b9f93cad | ||
|
|
fc177a9355 | ||
|
|
9ef2692885 | ||
|
|
35ada6b449 | ||
|
|
1a98a4d599 | ||
|
|
e5d7482133 | ||
|
|
69e987363a | ||
|
|
dad73a8447 | ||
|
|
f22b344d87 | ||
|
|
8d78b5b316 | ||
|
|
6024caf99c | ||
|
|
ff60c7be25 | ||
|
|
891e3dde5f | ||
|
|
ee4d3a70fa | ||
|
|
81c0e300a1 | ||
|
|
79abb132f5 | ||
|
|
d94518cbed | ||
|
|
6f862401ab | ||
|
|
aeae1dd66e | ||
|
|
8a032bea18 | ||
|
|
2c3ca2f885 | ||
|
|
d4a22682cd | ||
|
|
4c03041a47 | ||
|
|
c96cb95ec3 | ||
|
|
91923ef168 | ||
|
|
b086db14d9 | ||
|
|
369566f6f1 | ||
|
|
555bf36a7a | ||
|
|
81b27dab08 | ||
|
|
ecdd2c0f61 | ||
|
|
ef9dd72364 | ||
|
|
f2031b8308 | ||
|
|
6995f5627a | ||
|
|
2ad8deb6af | ||
|
|
8f0eea6e44 | ||
|
|
8859417f42 | ||
|
|
cc35110ff5 | ||
|
|
eed6f39b10 |
@@ -162,7 +162,7 @@
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
|
||||
7
jitpack.yml
Normal file
7
jitpack.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
before_install:
|
||||
- source "${HOME}/.sdkman/bin/sdkman-init.sh"
|
||||
- sdk update
|
||||
- sdk install java 17.0.10-tem
|
||||
- sdk use java 17.0.10-tem
|
||||
- sdk install maven 3.9.8
|
||||
- sdk use maven 3.9.8
|
||||
1
pom.xml
1
pom.xml
@@ -48,6 +48,7 @@
|
||||
<module>refaster-runner</module>
|
||||
<module>refaster-support</module>
|
||||
<module>refaster-test-support</module>
|
||||
<module>testng-junit-migrator</module>
|
||||
</modules>
|
||||
|
||||
<scm child.scm.developerConnection.inherit.append.path="false" child.scm.url.inherit.append.path="false">
|
||||
|
||||
147
testng-junit-migrator/README.md
Normal file
147
testng-junit-migrator/README.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# TestNG to JUnit Jupiter migrator
|
||||
|
||||
This module contains a tool to automatically migrate TestNG tests to JUnit
|
||||
Jupiter. The tool is built on top of [Error Prone][error-prone-orig-repo]. To
|
||||
use it, read the installation guide below.
|
||||
|
||||
### Installation
|
||||
|
||||
1. First, follow Error Prone's [installation
|
||||
guide][error-prone-installation-guide]. For extra information, see this
|
||||
[README][eps-readme]. (This step can be skipped for Picnic repositories!)
|
||||
2. Clone the Error Prone Support repository and checkout the branch
|
||||
`gdejong/testng-migrator`.
|
||||
3. Next, run `mvn versions:set -DnewVersion=0.17.1-testng-migration -DgenerateBackupPoms=false`.
|
||||
This will update set the version to `0.17.1-testng-migration`.
|
||||
4. Next, run `mvn clean install`. This will create a `0.17.1-testng-migrator` version
|
||||
of the `testng-junit-migrator` module. The version will now be available in your local Maven repository.
|
||||
5. Finally, add the following profile to your `pom.xml`. This should be the `pom.xml` in the root of your module.
|
||||
Usually this is the parent `pom.xml`, but single module projects are also supported.
|
||||
|
||||
```xml
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>testng-migrator</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths combine.children="append">
|
||||
<path>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>testng-junit-migrator</artifactId>
|
||||
<version>0.10.1-testng-migration</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
```
|
||||
|
||||
Having this profile allows the migration script to verify the correctness of
|
||||
the result by making sure the same amount of tests are executed.
|
||||
|
||||
## Run the migration
|
||||
|
||||
> **Note**
|
||||
> For Picnic repositories there is an extra step required _before_ running the
|
||||
> migration, see [here](#picnic-specific).
|
||||
|
||||
Now that the migration is set up, one can start the migration by executing the
|
||||
[run-testng-junit-migrator.sh][migration-script] script in the same directory as the `pom.xml` file we changed earlier.
|
||||
|
||||
This script will:
|
||||
|
||||
1. Add the required `JUnit` dependencies to your `pom.xml`.
|
||||
2. Run the `testng-to-junit` migration.
|
||||
|
||||
> **Note**
|
||||
> Please verify that the migrated code still compiles after each step of the compilation.
|
||||
|
||||
### Counting tests
|
||||
|
||||
The amount of tests executed before the migration can be counted using the `--count` flag:
|
||||
```sh
|
||||
./run-testng-junit-migrator.sh --count
|
||||
```
|
||||
This will count the amount of tests that are executed. This is recommended before running the migration
|
||||
to allow for comparison.
|
||||
|
||||
### Picnic specific
|
||||
|
||||
The `PicnicSupermarket/picnic-scratch` repository contains a helper script
|
||||
`java-platform/testng-junit-migration.sh` that migrates some more
|
||||
Picnic-specific code. This should be executed _before_ starting the actual
|
||||
migration.
|
||||
|
||||
> **Warning**
|
||||
> This is a warning for `macOs` users.
|
||||
> Make sure gnu-grep and gnu-sed are installed!
|
||||
>
|
||||
> ```brew install grep gnu-sed```
|
||||
|
||||
Continue with performing the actual migration [here](#run-the-migration).
|
||||
Afterward, run the `./picnic-shared-tools/patch.sh` script.
|
||||
|
||||
Now you are done! 🤘🚀
|
||||
|
||||
### Migration code example
|
||||
|
||||
Consider the following TestNG test class:
|
||||
|
||||
```java
|
||||
// TestNG code:
|
||||
@Test
|
||||
public class A {
|
||||
public void simpleTest() {}
|
||||
|
||||
@Test(priority = 2)
|
||||
public void priorityTest() {}
|
||||
|
||||
@DataProvider
|
||||
private static Object[][] dataProviderTestCases() {
|
||||
return new Object[]{{1}, {2}, {3}};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "dataProviderTestCases")
|
||||
public void dataProviderTest(int number) {}
|
||||
}
|
||||
```
|
||||
|
||||
This migration tool will turn this into the following:
|
||||
|
||||
```java
|
||||
// JUnit Jupiter code:
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class A {
|
||||
@Test
|
||||
void simpleTest() {}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void priorityTest() {}
|
||||
|
||||
private static Stream<Argument> dataProviderTestCases() {
|
||||
return Stream.of(arguments(1), arguments(2), arguments(3));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("dataProviderTestCases")
|
||||
public void dataProviderTest(int number) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Known limitations
|
||||
- Certain `@DataProvider` methods cannot be automatically migrated (e.g., `return Stream.of(...).toArray(Object[][]::new)`).
|
||||
- Some uncommon `@Test` attributes are not supported, such as `ignoreMissingDependencies` and `dependsOnMethods`.
|
||||
- Test setup and teardown methods `@{Before, After}Test` are migrated to `@{Before, After}Each` to avoid introducing breaking changes. `@{Before, After}All` require a static method, while `@{Before, After}Test` are instance methods.
|
||||
|
||||
[eps-readme]: ../README.md
|
||||
[error-prone-installation-guide]: https://errorprone.info/docs/installation#maven
|
||||
[error-prone-orig-repo]: https://github.com/google/error-prone
|
||||
[migration-script]: run-testng-junit-migration.sh
|
||||
156
testng-junit-migrator/pom.xml
Normal file
156
testng-junit-migrator/pom.xml
Normal file
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.16.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>testng-junit-migrator</artifactId>
|
||||
|
||||
<name>Picnic :: Error Prone Support :: TestNG JUnit Migrator</name>
|
||||
<description>A tool to migrate TestNG tests to JUnit</description>
|
||||
<url>https://error-prone.picnic.tech</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${groupId.error-prone}</groupId>
|
||||
<artifactId>error_prone_annotation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${groupId.error-prone}</groupId>
|
||||
<artifactId>error_prone_annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${groupId.error-prone}</groupId>
|
||||
<artifactId>error_prone_check_api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${groupId.error-prone}</groupId>
|
||||
<artifactId>error_prone_test_helpers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-compiler</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
<artifactId>auto-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.service</groupId>
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-11</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-templating</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths combine.children="append">
|
||||
<path>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-compiler</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs combine.children="append">
|
||||
<arg>-Xplugin:RefasterRuleCompiler</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
142
testng-junit-migrator/run-testng-junit-migration.sh
Executable file
142
testng-junit-migrator/run-testng-junit-migration.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -u -o pipefail
|
||||
|
||||
# If this is not a Maven build, exit here to skip Maven-specific steps.
|
||||
if [ ! -f pom.xml ]; then
|
||||
echo "Not a Maven build, exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
function insert_dependency() {
|
||||
groupId=${1:?groupId not specified or empty}
|
||||
artifactId=${2:?artifactId not specified or empty}
|
||||
classifier=${3?classifier not specified}
|
||||
scope=${4?scope not specified}
|
||||
pomFile=${5:-pom.xml}
|
||||
|
||||
# If the dependency declaration is already present (irrespective of scope),
|
||||
# then we don't modify the file.
|
||||
xmlstarlet sel -T -N 'x=http://maven.apache.org/POM/4.0.0' \
|
||||
-t -m "/x:project/x:dependencies/x:dependency[
|
||||
x:groupId/text() = '${groupId}' and
|
||||
x:artifactId/text() = '${artifactId}' and
|
||||
(x:classifier/text() = '${classifier}' or '${classifier}' = '')
|
||||
]" -nl "${pomFile}" && return 0
|
||||
|
||||
# Determine the index at which to insert the dependency declaration.
|
||||
insertionIndex="$(
|
||||
(xmlstarlet sel -T -N 'x=http://maven.apache.org/POM/4.0.0' \
|
||||
-t -m '/x:project/x:dependencies/x:dependency' \
|
||||
-v 'concat(x:groupId, " : ", x:artifactId, " : ", x:classifier, " : ", x:scope)' -nl \
|
||||
"${pomFile}" || true) |
|
||||
awk "\$0 < \"${groupId} : ${artifactId} : ${classifier} : ${scope}\"" |
|
||||
wc -l
|
||||
)"
|
||||
|
||||
# Generate a placeholder that will be inserted at the place where the new
|
||||
# dependency declaration should reside. We need to jump through this hoop
|
||||
# because `xmlstarlet` does not support insertion of complex XML
|
||||
# sub-documents.
|
||||
placeholder="$(head -c 30 /dev/urandom | base64 | $sed_command 's,[^a-zA-Z0-9],,g')"
|
||||
|
||||
# Insert the placeholder. (Note that only one case will match.)
|
||||
xmlstarlet ed -L -P -N 'x=http://maven.apache.org/POM/4.0.0' \
|
||||
-s "/x:project[not(x:dependencies)]" \
|
||||
-t elem -n dependencies -v "${placeholder}" \
|
||||
-i "/x:project/x:dependencies/x:dependency[${insertionIndex} = 0 and position() = 1]" \
|
||||
-t text -n placeholder -v "${placeholder}" \
|
||||
-a "/x:project/x:dependencies/x:dependency[${insertionIndex}]" \
|
||||
-t text -n placeholder -v "${placeholder}" \
|
||||
"${pomFile}"
|
||||
|
||||
# Generate the XML subdocument we _actually_ want to insert.
|
||||
decl="$(echo "
|
||||
<dependency>
|
||||
<groupId>${groupId}</groupId>
|
||||
<artifactId>${artifactId}</artifactId>
|
||||
<classifier>${classifier}</classifier>
|
||||
<scope>${scope}</scope>
|
||||
</dependency>" |
|
||||
$sed_command '/></d' |
|
||||
$sed_command ':a;N;$!ba;s/\n/\\n/g')"
|
||||
|
||||
# Replace the placeholder with the actual dependency declaration.
|
||||
$sed_command -i "s,${placeholder},${decl}," "${pomFile}"
|
||||
}
|
||||
|
||||
if [[ -n "${1-}" ]] && [[ "${1}" == "--count" ]]; then
|
||||
echo "Counting number of tests..."
|
||||
test_results=$(mvn test | $grep_command -n "Results:" -A 3 | $grep_command -oP "Tests run: \K\d+(?=,)" | awk '{s+=$1} END {print s}')
|
||||
echo "Number of tests run: $test_results"
|
||||
exit
|
||||
fi
|
||||
|
||||
function handle_file() {
|
||||
module=${1:?module not specified or empty}
|
||||
groupId=${2:?groupId not specified or empty}
|
||||
artifactId=${3:?artifactId not specified or empty}
|
||||
classifier=${4?classifier not specified}
|
||||
scope=${5?scope not specified}
|
||||
pomFile=${6:-pom.xml}
|
||||
|
||||
if [[ -d $module ]] && [[ -f "$module/pom.xml" ]]; then
|
||||
cd "$module"
|
||||
insert_dependency "$groupId" "$artifactId" "$classifier" "$scope"
|
||||
echo "[$module] Added $groupId:$artifactId"
|
||||
cd -
|
||||
fi
|
||||
}
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux*)
|
||||
grep_command="grep"
|
||||
sed_command="sed"
|
||||
;;
|
||||
Darwin*)
|
||||
grep_command="ggrep"
|
||||
sed_command="gsed"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported distribution $(uname -s) for this script."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Migrating to JUnit 5..."
|
||||
echo "Adding required dependencies..."
|
||||
|
||||
if $grep_command -q "<packaging>pom</packaging>" "pom.xml"; then
|
||||
|
||||
for module in $($grep_command -rl "org.testng.annotations.Test" $(pwd) | awk -F "$(pwd)" '{print $2}' | awk -F '/' '{print $2}' | uniq); do
|
||||
(
|
||||
handle_file "$module" "org.junit.jupiter" "junit-jupiter-api" "" "test"
|
||||
)
|
||||
(
|
||||
handle_file "$module" "org.junit.jupiter" "junit-jupiter-engine" "" "test"
|
||||
)
|
||||
done
|
||||
for module in $($grep_command -rl "org.testng.annotations.DataProvider" $(pwd) | awk -F "$(pwd)" '{print $2}' | awk -F '/' '{print $2}' | uniq); do
|
||||
(
|
||||
handle_file "$module" "org.junit.jupiter" "junit-jupiter-params" "" "test"
|
||||
)
|
||||
done
|
||||
else
|
||||
if $grep_command -rq "org.testng.annotations.Test" "src/"; then
|
||||
handle_file "./" "org.junit.jupiter" "junit-jupiter-api" "" "test"
|
||||
handle_file "./" "org.junit.jupiter" "junit-jupiter-engine" "" "test"
|
||||
fi
|
||||
if $grep_command -rq "org.testng.annotations.DataProvider" "src/"; then
|
||||
handle_file "./" "org.junit.jupiter" "junit-jupiter-params" "" "test"
|
||||
fi
|
||||
fi
|
||||
echo "Running migration..."
|
||||
mvn \
|
||||
-Perror-prone \
|
||||
-Ptestng-migrator \
|
||||
-Ppatch \
|
||||
clean test-compile fmt:format \
|
||||
-Derror-prone.patch-checks="TestNGJUnitMigration" \
|
||||
-Dverification.skip
|
||||
|
||||
echo "Finished executing migration!"
|
||||
@@ -0,0 +1,29 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* Interface implemented by classes that define how to migrate a specific attribute from a TestNG
|
||||
* {@code Test} annotation to JUnit.
|
||||
*/
|
||||
@Immutable
|
||||
interface AttributeMigrator {
|
||||
/**
|
||||
* Attempts to create a {@link SuggestedFix}.
|
||||
*
|
||||
* @param methodTree The method tree the annotation is on.
|
||||
* @param state The visitor state.
|
||||
* @return an {@link Optional} containing the created fix. This returns an {@link
|
||||
* Optional#empty()} if the {@link AttributeMigrator} is not able to migrate the attribute.
|
||||
*/
|
||||
Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* A {@link AttributeMigrator} that migrates the {@code org.testng.annotations.Test#dataProvider}
|
||||
* attributes.
|
||||
*/
|
||||
@Immutable
|
||||
final class DataProviderAttributeMigrator implements AttributeMigrator {
|
||||
@Override
|
||||
public Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
ExpressionTree dataProviderNameExpressionTree = annotation.getAttributes().get("dataProvider");
|
||||
if (dataProviderNameExpressionTree == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String dataProviderName = ASTHelpers.constValue(dataProviderNameExpressionTree, String.class);
|
||||
if (!metadata.getDataProviderMetadata().containsKey(dataProviderName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(
|
||||
SuggestedFix.builder()
|
||||
.addImport("org.junit.jupiter.params.ParameterizedTest")
|
||||
.addImport("org.junit.jupiter.params.provider.MethodSource")
|
||||
.prefixWith(methodTree, "@ParameterizedTest\n")
|
||||
.prefixWith(methodTree, String.format("@MethodSource(\"%s\")%n", dataProviderName))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.sun.source.tree.Tree.Kind.NEW_ARRAY;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.ErrorProneToken;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.NewArrayTree;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import com.sun.tools.javac.parser.Tokens.Comment;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.util.SourceCode;
|
||||
|
||||
// XXX: Can this one also implement a `Migrator`?
|
||||
/** A helper class that migrates a TestNG {@code DataProvider} to a JUnit {@code MethodSource}. */
|
||||
final class DataProviderMigrator {
|
||||
/** This regular expression replaces matches instances of `this.getClass()` and `getClass()`. */
|
||||
private static final Pattern GET_CLASS =
|
||||
Pattern.compile("((?<!\\b\\.)|(\\bthis\\.))(getClass\\(\\))");
|
||||
|
||||
private DataProviderMigrator() {}
|
||||
|
||||
/**
|
||||
* Tells whether the specified {@code DataProvider} can be migrated.
|
||||
*
|
||||
* @param methodTree The dataprovider methode tree.
|
||||
* @return {@code true} if the data provider can be migrated or else {@code false}.
|
||||
*/
|
||||
static boolean canFix(MethodTree methodTree) {
|
||||
return getDataProviderReturnTree(getReturnTree(methodTree)).isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link SuggestedFix} required to migrate a TestNG {@code DataProvider} to a JUnit
|
||||
* {@code MethodSource}.
|
||||
*
|
||||
* @param classTree The class containing the data provider.
|
||||
* @param methodTree The data provider method.
|
||||
* @param state The {@link VisitorState}.
|
||||
* @return An {@link Optional} containing the created fix.
|
||||
*/
|
||||
static Optional<SuggestedFix> createFix(
|
||||
ClassTree classTree, MethodTree methodTree, VisitorState state) {
|
||||
return tryMigrateDataProvider(methodTree, classTree, state);
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix> tryMigrateDataProvider(
|
||||
MethodTree methodTree, ClassTree classTree, VisitorState state) {
|
||||
ReturnTree returnTree = getReturnTree(methodTree);
|
||||
|
||||
return getDataProviderReturnTree(returnTree)
|
||||
.map(
|
||||
dataProviderReturnTree ->
|
||||
SuggestedFix.builder()
|
||||
.addStaticImport("org.junit.jupiter.params.provider.Arguments.arguments")
|
||||
.addImport(Stream.class.getCanonicalName())
|
||||
.addImport("org.junit.jupiter.params.provider.Arguments")
|
||||
.delete(methodTree)
|
||||
.postfixWith(
|
||||
methodTree,
|
||||
buildMethodSource(
|
||||
classTree.getSimpleName().toString(),
|
||||
methodTree.getName().toString(),
|
||||
methodTree,
|
||||
returnTree,
|
||||
dataProviderReturnTree,
|
||||
state))
|
||||
.build());
|
||||
}
|
||||
|
||||
private static ReturnTree getReturnTree(MethodTree methodTree) {
|
||||
return methodTree.getBody().getStatements().stream()
|
||||
.filter(ReturnTree.class::isInstance)
|
||||
.findFirst()
|
||||
.map(ReturnTree.class::cast)
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private static Optional<NewArrayTree> getDataProviderReturnTree(ReturnTree returnTree) {
|
||||
if (returnTree.getExpression().getKind() != NEW_ARRAY
|
||||
|| ((NewArrayTree) returnTree.getExpression()).getInitializers().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of((NewArrayTree) returnTree.getExpression());
|
||||
}
|
||||
|
||||
private static String buildMethodSource(
|
||||
String className,
|
||||
String name,
|
||||
MethodTree methodTree,
|
||||
ReturnTree returnTree,
|
||||
NewArrayTree newArrayTree,
|
||||
VisitorState state) {
|
||||
StringBuilder sourceBuilder =
|
||||
new StringBuilder()
|
||||
.append(" private static Stream<Arguments> ")
|
||||
.append(name)
|
||||
.append(" () ");
|
||||
|
||||
if (!methodTree.getThrows().isEmpty()) {
|
||||
sourceBuilder
|
||||
.append(" throws ")
|
||||
.append(
|
||||
methodTree.getThrows().stream()
|
||||
.filter(IdentifierTree.class::isInstance)
|
||||
.map(IdentifierTree.class::cast)
|
||||
.map(identifierTree -> identifierTree.getName().toString())
|
||||
.collect(joining(", ")));
|
||||
}
|
||||
|
||||
return sourceBuilder
|
||||
.append(" {\n")
|
||||
.append(extractMethodBodyWithoutReturnStatement(methodTree, returnTree, state))
|
||||
.append(" return ")
|
||||
.append(buildArgumentStream(className, newArrayTree, state))
|
||||
.append(";\n}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
private static String extractMethodBodyWithoutReturnStatement(
|
||||
MethodTree methodTree, ReturnTree returnTree, VisitorState state) {
|
||||
String body = SourceCode.treeToString(methodTree.getBody(), state);
|
||||
return body.substring(2, body.indexOf(SourceCode.treeToString(returnTree, state)) - 1);
|
||||
}
|
||||
|
||||
private static String buildArgumentStream(
|
||||
String className, NewArrayTree newArrayTree, VisitorState state) {
|
||||
int startPos = ASTHelpers.getStartPosition(newArrayTree);
|
||||
int endPos = state.getEndPosition(newArrayTree);
|
||||
ImmutableMap<Integer, List<Comment>> comments =
|
||||
state.getOffsetTokens(startPos, endPos).stream()
|
||||
.collect(toImmutableMap(ErrorProneToken::pos, ErrorProneToken::comments));
|
||||
|
||||
StringBuilder argumentsBuilder = new StringBuilder();
|
||||
argumentsBuilder.append(
|
||||
newArrayTree.getInitializers().stream()
|
||||
.map(
|
||||
expression ->
|
||||
wrapTestValueWithArguments(
|
||||
expression,
|
||||
comments.getOrDefault(
|
||||
ASTHelpers.getStartPosition(expression), ImmutableList.of()),
|
||||
state))
|
||||
.collect(joining(",")));
|
||||
|
||||
/*
|
||||
* This replaces all instances of `{,this.}getClass()` with the fully qualified class name to
|
||||
* retain functionality in static context.
|
||||
*/
|
||||
return GET_CLASS
|
||||
.matcher(String.format("Stream.of(%s%n)", argumentsBuilder))
|
||||
.replaceAll(className + ".class");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a value in {@code org.junit.jupiter.params.provider#arguments()}.
|
||||
*
|
||||
* <p>Drops curly braces from array initialisation values.
|
||||
*/
|
||||
private static String wrapTestValueWithArguments(
|
||||
ExpressionTree tree, List<Comment> comments, VisitorState state) {
|
||||
String source = SourceCode.treeToString(tree, state);
|
||||
|
||||
String argumentValue =
|
||||
tree.getKind() == NEW_ARRAY ? source.substring(1, source.length() - 1) : source;
|
||||
|
||||
return String.format(
|
||||
"\t\t%s%n\t\targuments(%s)",
|
||||
comments.stream().map(Comment::getText).collect(joining("\n")), argumentValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
import tech.picnic.errorprone.util.SourceCode;
|
||||
|
||||
/** A {@link AttributeMigrator} that migrates the {@code description} attribute. */
|
||||
@Immutable
|
||||
final class DescriptionAttributeMigrator implements AttributeMigrator {
|
||||
@Override
|
||||
public Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
return Optional.ofNullable(annotation.getAttributes().get("description"))
|
||||
.map(
|
||||
description ->
|
||||
SuggestedFix.builder()
|
||||
.addImport("org.junit.jupiter.api.DisplayName")
|
||||
.prefixWith(
|
||||
methodTree,
|
||||
String.format(
|
||||
"@DisplayName(%s)%n", SourceCode.treeToString(description, state)))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
|
||||
/** A {@link AttributeMigrator} that migrates the {@code enabled} attribute. */
|
||||
@Immutable
|
||||
final class EnabledAttributeMigrator implements AttributeMigrator {
|
||||
@Override
|
||||
public Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
return Optional.ofNullable(annotation.getAttributes().get("enabled"))
|
||||
.map(enabled -> ((LiteralTree) enabled).getValue())
|
||||
.filter(Boolean.FALSE::equals)
|
||||
.map(
|
||||
unused ->
|
||||
SuggestedFix.builder()
|
||||
.addImport("org.junit.jupiter.api.Disabled")
|
||||
.prefixWith(methodTree, "@Disabled\n")
|
||||
.build())
|
||||
.or(() -> Optional.of(SuggestedFix.emptyFix()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.auto.common.MoreStreams.toImmutableList;
|
||||
import static com.sun.source.tree.Tree.Kind.MEMBER_SELECT;
|
||||
import static com.sun.source.tree.Tree.Kind.NEW_ARRAY;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.BlockTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.NewArrayTree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
import tech.picnic.errorprone.util.SourceCode;
|
||||
|
||||
/** A {@link AttributeMigrator} that migrates the {@code expectedExceptions} attribute. */
|
||||
@Immutable
|
||||
final class ExpectedExceptionsAttributeMigrator implements AttributeMigrator {
|
||||
@Override
|
||||
public Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
return Optional.ofNullable(annotation.getAttributes().get("expectedExceptions"))
|
||||
.map(
|
||||
expectedExceptions ->
|
||||
getExpectedException(expectedExceptions, state)
|
||||
.map(
|
||||
expectedException -> {
|
||||
SuggestedFix.Builder fix =
|
||||
SuggestedFix.builder()
|
||||
.replace(
|
||||
methodTree.getBody(),
|
||||
buildWrappedBody(
|
||||
methodTree.getBody(), expectedException, state));
|
||||
ImmutableList<String> removedExceptions =
|
||||
getRemovedExceptions(expectedExceptions, state);
|
||||
if (!removedExceptions.isEmpty()) {
|
||||
fix.prefixWith(
|
||||
methodTree,
|
||||
String.format(
|
||||
"// XXX: Removed handling of `%s` because this migration doesn't support%n// XXX: multiple expected exceptions.%n",
|
||||
String.join(", ", removedExceptions)));
|
||||
}
|
||||
|
||||
return fix.build();
|
||||
})
|
||||
.orElseGet(SuggestedFix::emptyFix));
|
||||
}
|
||||
|
||||
private static Optional<String> getExpectedException(
|
||||
ExpressionTree expectedExceptions, VisitorState state) {
|
||||
if (expectedExceptions.getKind() == NEW_ARRAY) {
|
||||
NewArrayTree arrayTree = (NewArrayTree) expectedExceptions;
|
||||
if (arrayTree.getInitializers().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(SourceCode.treeToString(arrayTree.getInitializers().get(0), state));
|
||||
} else if (expectedExceptions.getKind() == MEMBER_SELECT) {
|
||||
return Optional.of(SourceCode.treeToString(expectedExceptions, state));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static ImmutableList<String> getRemovedExceptions(
|
||||
ExpressionTree expectedExceptions, VisitorState state) {
|
||||
if (expectedExceptions.getKind() != NEW_ARRAY) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
NewArrayTree arrayTree = (NewArrayTree) expectedExceptions;
|
||||
return arrayTree.getInitializers().subList(1, arrayTree.getInitializers().size()).stream()
|
||||
.map(initializer -> SourceCode.treeToString(initializer, state))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static String buildWrappedBody(BlockTree tree, String exception, VisitorState state) {
|
||||
return String.format(
|
||||
"{%norg.junit.jupiter.api.Assertions.assertThrows(%s, () -> %s);%n}",
|
||||
exception, SourceCode.treeToString(tree, state));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.NewArrayTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
import tech.picnic.errorprone.util.SourceCode;
|
||||
|
||||
/** A {@link AttributeMigrator} that migrates the {@code group} attribute. */
|
||||
@Immutable
|
||||
final class GroupsAttributeMigrator implements AttributeMigrator {
|
||||
@Override
|
||||
public Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
ExpressionTree groupsExpression = annotation.getAttributes().get("groups");
|
||||
if (groupsExpression == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
ImmutableList<String> groups = extractGroups(groupsExpression, state);
|
||||
if (!groups.stream().allMatch(GroupsAttributeMigrator::isValidTagName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder().addImport("org.junit.jupiter.api.Tag");
|
||||
groups.forEach(group -> fix.prefixWith(methodTree, String.format("@Tag(\"%s\")%n", group)));
|
||||
|
||||
return Optional.of(fix.build());
|
||||
}
|
||||
|
||||
private static boolean isValidTagName(String tagName) {
|
||||
return !tagName.isEmpty() && tagName.chars().noneMatch(Character::isISOControl);
|
||||
}
|
||||
|
||||
private static ImmutableList<String> extractGroups(ExpressionTree dataValue, VisitorState state) {
|
||||
if (dataValue.getKind() == Tree.Kind.STRING_LITERAL) {
|
||||
return ImmutableList.of(trimTagName(SourceCode.treeToString(dataValue, state)));
|
||||
}
|
||||
|
||||
NewArrayTree groupsTree = (NewArrayTree) dataValue;
|
||||
return groupsTree.getInitializers().stream()
|
||||
.map(initializer -> trimTagName(SourceCode.treeToString(initializer, state)))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static String trimTagName(String tagName) {
|
||||
return tagName.replaceAll("(^\")|(\"$)", "").trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
import tech.picnic.errorprone.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link AttributeMigrator} that migrates the {@code org.testng.annotations.Test#priority}
|
||||
* attribute.
|
||||
*/
|
||||
@Immutable
|
||||
final class PriorityAttributeMigrator implements AttributeMigrator {
|
||||
@Override
|
||||
public Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
return Optional.ofNullable(annotation.getAttributes().get("priority"))
|
||||
.map(
|
||||
priority ->
|
||||
SuggestedFix.builder()
|
||||
.addImport("org.junit.jupiter.api.Order")
|
||||
.addImport("org.junit.jupiter.api.TestMethodOrder")
|
||||
.addImport("org.junit.jupiter.api.MethodOrderer")
|
||||
.prefixWith(
|
||||
methodTree,
|
||||
String.format("@Order(%s)%n", SourceCode.treeToString(priority, state)))
|
||||
.prefixWith(
|
||||
metadata.getClassTree(),
|
||||
"@TestMethodOrder(MethodOrderer.OrderAnnotation.class)\n")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.SetupTeardownType;
|
||||
|
||||
/**
|
||||
* A helper class that migrates TestNG setup and teardown methods to their JUnit Jupiter equivalent.
|
||||
*/
|
||||
final class SetupTeardownMethodMigrator {
|
||||
private SetupTeardownMethodMigrator() {}
|
||||
|
||||
/**
|
||||
* Create the {@link SuggestedFix} required to migrate a TestNG setup/teardown methods to the
|
||||
* JUnit Jupiter variant.
|
||||
*
|
||||
* @param tree The setup/teardown method tree.
|
||||
* @param type The setup/teardown type.
|
||||
* @param state The visitor state.
|
||||
* @return An {@link Optional} containing the created fix.
|
||||
*/
|
||||
static Optional<SuggestedFix> createFix(
|
||||
MethodTree tree, SetupTeardownType type, VisitorState state) {
|
||||
return getSetupTeardownAnnotationTree(tree, type, state)
|
||||
.map(
|
||||
annotation -> {
|
||||
SuggestedFix.Builder fix =
|
||||
SuggestedFix.builder()
|
||||
.replace(annotation, String.format("@%s", type.getJunitAnnotationClass()));
|
||||
if (type.requiresStaticMethod()
|
||||
&& !tree.getModifiers().getFlags().contains(Modifier.STATIC)) {
|
||||
SuggestedFixes.addModifiers(tree, state, Modifier.STATIC).ifPresent(fix::merge);
|
||||
}
|
||||
|
||||
return fix.build();
|
||||
});
|
||||
}
|
||||
|
||||
private static Optional<? extends AnnotationTree> getSetupTeardownAnnotationTree(
|
||||
MethodTree tree, SetupTeardownType type, VisitorState state) {
|
||||
return ASTHelpers.getAnnotations(tree).stream()
|
||||
.filter(annotation -> type.getAnnotationMatcher().matches(annotation, state))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/** The annotation attributes that are supported by the TestNG to JUnit Jupiter migration. */
|
||||
enum TestAnnotationAttribute {
|
||||
DATA_PROVIDER("dataProvider", new DataProviderAttributeMigrator()),
|
||||
DESCRIPTION("description", new DescriptionAttributeMigrator()),
|
||||
ENABLED("enabled", new EnabledAttributeMigrator()),
|
||||
EXPECTED_EXCEPTIONS("expectedExceptions", new ExpectedExceptionsAttributeMigrator()),
|
||||
GROUPS("groups", new GroupsAttributeMigrator()),
|
||||
PRIORITY("priority", new PriorityAttributeMigrator()),
|
||||
TIMEOUT("timeOut", new TimeOutAttributeMigrator());
|
||||
|
||||
private final String name;
|
||||
private final AttributeMigrator attributeMigrator;
|
||||
|
||||
TestAnnotationAttribute(String name, AttributeMigrator attributeMigrator) {
|
||||
this.name = name;
|
||||
this.attributeMigrator = attributeMigrator;
|
||||
}
|
||||
|
||||
AttributeMigrator getAttributeMigrator() {
|
||||
return attributeMigrator;
|
||||
}
|
||||
|
||||
static Optional<TestAnnotationAttribute> fromString(String attribute) {
|
||||
return stream(values()).filter(v -> v.name.equals(attribute)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.REFACTORING;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.DataProviderMetadata;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.SetupTeardownType;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that migrates TestNG unit tests to JUnit 5.
|
||||
*
|
||||
* <p>Supported TestNG annotation attributes are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code dataProvider}
|
||||
* <li>{@code description}
|
||||
* <li>{@code isEnabled}
|
||||
* <li>{@code expectedExceptions}
|
||||
* <li>{@code priority}
|
||||
* <li>{@code groups}
|
||||
* </ul>
|
||||
*
|
||||
* This migration will also take care of any setup/teardown methods.
|
||||
*
|
||||
* <p>Note: As the {@code @BeforeAll} and {@code @AfterAll} methods in JUnit are required to be
|
||||
* static, this <em>might</em> introduce breaking changes.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Migrate TestNG tests to their JUnit equivalent",
|
||||
linkType = NONE,
|
||||
tags = REFACTORING,
|
||||
severity = ERROR)
|
||||
public final class TestNGJUnitMigration extends BugChecker implements CompilationUnitTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String CONSERVATIVE_MIGRATION_MODE_FLAG =
|
||||
"TestNGJUnitMigration:ConservativeMode";
|
||||
|
||||
private final boolean conservativeMode;
|
||||
|
||||
/**
|
||||
* Instantiates a new {@link TestNGJUnitMigration} instance. This will default to the aggressive
|
||||
* migration mode.
|
||||
*/
|
||||
public TestNGJUnitMigration() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new {@link TestNGJUnitMigration} with the specified {@link ErrorProneFlags}.
|
||||
*
|
||||
* @param flags The Error Prone flags used to set the migration mode.
|
||||
*/
|
||||
@Inject
|
||||
TestNGJUnitMigration(ErrorProneFlags flags) {
|
||||
conservativeMode = flags.getBoolean(CONSERVATIVE_MIGRATION_MODE_FLAG).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
|
||||
TestNGScanner scanner = new TestNGScanner(state);
|
||||
ImmutableMap<ClassTree, TestNgMetadata> classMetaData = scanner.collectMetadataForClasses(tree);
|
||||
|
||||
new TreeScanner<@Nullable Void, TestNgMetadata>() {
|
||||
@Override
|
||||
public @Nullable Void visitClass(ClassTree node, TestNgMetadata testNgMetadata) {
|
||||
TestNgMetadata metadata = classMetaData.get(node);
|
||||
if (metadata == null) {
|
||||
return super.visitClass(node, testNgMetadata);
|
||||
}
|
||||
|
||||
for (DataProviderMetadata dataProviderMetadata : metadata.getDataProvidersInUse()) {
|
||||
DataProviderMigrator.createFix(
|
||||
metadata.getClassTree(), dataProviderMetadata.getMethodTree(), state)
|
||||
.ifPresent(
|
||||
fix ->
|
||||
state.reportMatch(
|
||||
describeMatch(
|
||||
dataProviderMetadata.getMethodTree(),
|
||||
fix.toBuilder().removeStaticImport("org.testng.Assert.*").build())));
|
||||
}
|
||||
|
||||
for (Entry<MethodTree, SetupTeardownType> entry : metadata.getSetupTeardown().entrySet()) {
|
||||
SetupTeardownMethodMigrator.createFix(entry.getKey(), entry.getValue(), state)
|
||||
.ifPresent(fix -> state.reportMatch(describeMatch(entry.getKey(), fix)));
|
||||
}
|
||||
|
||||
super.visitClass(node, metadata);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitMethod(MethodTree tree, TestNgMetadata metadata) {
|
||||
/* Make sure ALL Tests in the class can be migrated. */
|
||||
if (conservativeMode && !canMigrateAllTestsInClass(metadata, state)) {
|
||||
return super.visitMethod(tree, metadata);
|
||||
}
|
||||
|
||||
metadata
|
||||
.getAnnotation(tree)
|
||||
.ifPresent(
|
||||
annotation -> {
|
||||
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
|
||||
buildAttributeFixes(metadata, annotation, tree, state).forEach(fixBuilder::merge);
|
||||
|
||||
fixBuilder.merge(migrateAnnotation(annotation, tree));
|
||||
state.reportMatch(describeMatch(tree, fixBuilder.build()));
|
||||
});
|
||||
return super.visitMethod(tree, metadata);
|
||||
}
|
||||
}.scan(tree, null);
|
||||
|
||||
/* All suggested fixes are already directly reported to the `VisitorState`. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private static ImmutableList<SuggestedFix> buildAttributeFixes(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotationMetadata,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
return annotationMetadata.getAttributes().entrySet().stream()
|
||||
.flatMap(
|
||||
entry ->
|
||||
trySuggestFix(metadata, annotationMetadata, entry.getKey(), methodTree, state)
|
||||
.stream())
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static boolean canMigrateTest(
|
||||
MethodTree methodTree,
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotationMetadata,
|
||||
VisitorState state) {
|
||||
ImmutableList<TestAnnotationAttribute> attributes =
|
||||
annotationMetadata.getAttributes().keySet().stream()
|
||||
.map(TestAnnotationAttribute::fromString)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(toImmutableList());
|
||||
return (annotationMetadata.getAttributes().isEmpty() || !attributes.isEmpty())
|
||||
&& attributes.stream()
|
||||
.allMatch(
|
||||
kind ->
|
||||
kind.getAttributeMigrator()
|
||||
.migrate(metadata, annotationMetadata, methodTree, state)
|
||||
.isPresent());
|
||||
}
|
||||
|
||||
private static boolean canMigrateAllTestsInClass(TestNgMetadata metadata, VisitorState state) {
|
||||
return metadata.getMethodAnnotations().entrySet().stream()
|
||||
.allMatch(entry -> canMigrateTest(entry.getKey(), metadata, entry.getValue(), state));
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix> trySuggestFix(
|
||||
TestNgMetadata metadata,
|
||||
AnnotationMetadata annotation,
|
||||
String attributeName,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
return TestAnnotationAttribute.fromString(attributeName)
|
||||
.map(TestAnnotationAttribute::getAttributeMigrator)
|
||||
.flatMap(migrator -> migrator.migrate(metadata, annotation, methodTree, state))
|
||||
.or(
|
||||
() ->
|
||||
UnsupportedAttributeMigrator.migrate(annotation, methodTree, attributeName, state));
|
||||
}
|
||||
|
||||
private static SuggestedFix migrateAnnotation(
|
||||
AnnotationMetadata annotationMetadata, MethodTree methodTree) {
|
||||
SuggestedFix.Builder fixBuilder =
|
||||
SuggestedFix.builder().delete(annotationMetadata.getAnnotationTree());
|
||||
if (!annotationMetadata.getAttributes().containsKey("dataProvider")) {
|
||||
fixBuilder.prefixWith(methodTree, "@org.junit.jupiter.api.Test\n ");
|
||||
}
|
||||
|
||||
return fixBuilder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.matchers.TestNgMatchers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
|
||||
/**
|
||||
* A collection of TestNG-specific helper methods and {@link Matcher}s.
|
||||
*
|
||||
* <p>These constants and methods are additions to the ones found in {@link TestNgMatchers}.
|
||||
*/
|
||||
final class TestNGMatchers {
|
||||
/**
|
||||
* Matches the TestNG {@code Test} annotation specifically. As {@link
|
||||
* TestNgMatchers#hasTestNgAnnotation(ClassTree)} also other TestNG annotations.
|
||||
*/
|
||||
public static final Matcher<AnnotationTree> TESTNG_TEST_ANNOTATION =
|
||||
isType("org.testng.annotations.Test");
|
||||
|
||||
/** Matches the TestNG {@code DataProvider} annotation specifically. */
|
||||
public static final Matcher<MethodTree> TESTNG_VALUE_FACTORY_METHOD =
|
||||
hasAnnotation("org.testng.annotations.DataProvider");
|
||||
|
||||
private TestNGMatchers() {}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
|
||||
import static com.google.errorprone.matchers.Matchers.hasModifier;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static tech.picnic.errorprone.testngjunit.TestNGMatchers.TESTNG_TEST_ANNOTATION;
|
||||
import static tech.picnic.errorprone.testngjunit.TestNGMatchers.TESTNG_VALUE_FACTORY_METHOD;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.DataProviderMetadata;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.SetupTeardownType;
|
||||
|
||||
/**
|
||||
* A {@link TreeScanner} which will scan a {@link com.sun.source.tree.CompilationUnitTree} and
|
||||
* collect data required for the migration from each class in the compilation unit.
|
||||
*
|
||||
* <p>This data can be retrieved using {@link #collectMetadataForClasses(CompilationUnitTree)}.
|
||||
*/
|
||||
final class TestNGScanner extends TreeScanner<@Nullable Void, TestNgMetadata.Builder> {
|
||||
private static final Matcher<MethodTree> TESTNG_TEST_METHOD =
|
||||
anyOf(
|
||||
hasAnnotation("org.testng.annotations.Test"),
|
||||
allOf(hasModifier(Modifier.PUBLIC), not(hasModifier(Modifier.STATIC))));
|
||||
|
||||
private final ImmutableMap.Builder<ClassTree, TestNgMetadata> metadataBuilder =
|
||||
ImmutableMap.builder();
|
||||
private final VisitorState state;
|
||||
|
||||
TestNGScanner(VisitorState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitClass(ClassTree tree, TestNgMetadata.Builder unused) {
|
||||
TestNgMetadata.Builder builder = TestNgMetadata.builder();
|
||||
builder.setClassTree(tree);
|
||||
getTestNgAnnotation(tree, state).ifPresent(builder::setClassLevelAnnotationMetadata);
|
||||
super.visitClass(tree, builder);
|
||||
metadataBuilder.put(tree, builder.build());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitMethod(MethodTree tree, TestNgMetadata.Builder builder) {
|
||||
if (ASTHelpers.isGeneratedConstructor(tree)) {
|
||||
return super.visitMethod(tree, builder);
|
||||
}
|
||||
|
||||
if (TESTNG_VALUE_FACTORY_METHOD.matches(tree, state) && DataProviderMigrator.canFix(tree)) {
|
||||
builder
|
||||
.dataProviderMetadataBuilder()
|
||||
.put(tree.getName().toString(), DataProviderMetadata.create(tree));
|
||||
return super.visitMethod(tree, builder);
|
||||
}
|
||||
|
||||
Optional<SetupTeardownType> setupTeardownType = SetupTeardownType.matchType(tree, state);
|
||||
if (setupTeardownType.isPresent()) {
|
||||
builder.setupTeardownBuilder().put(tree, setupTeardownType.orElseThrow());
|
||||
return super.visitMethod(tree, builder);
|
||||
}
|
||||
|
||||
if (TESTNG_TEST_METHOD.matches(tree, state)) {
|
||||
getTestNgAnnotation(tree, state)
|
||||
.or(builder::getClassLevelAnnotationMetadata)
|
||||
.ifPresent(annotation -> builder.methodAnnotationsBuilder().put(tree, annotation));
|
||||
}
|
||||
|
||||
return super.visitMethod(tree, builder);
|
||||
}
|
||||
|
||||
public ImmutableMap<ClassTree, TestNgMetadata> collectMetadataForClasses(
|
||||
CompilationUnitTree tree) {
|
||||
scan(tree, null);
|
||||
return metadataBuilder.build();
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
private static Optional<AnnotationMetadata> getTestNgAnnotation(Tree tree, VisitorState state) {
|
||||
return ASTHelpers.getAnnotations(tree).stream()
|
||||
.filter(annotation -> TESTNG_TEST_ANNOTATION.matches(annotation, state))
|
||||
.findFirst()
|
||||
.map(
|
||||
annotationTree ->
|
||||
AnnotationMetadata.create(
|
||||
annotationTree,
|
||||
annotationTree.getArguments().stream()
|
||||
.filter(AssignmentTree.class::isInstance)
|
||||
.map(AssignmentTree.class::cast)
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
assignment ->
|
||||
((IdentifierTree) assignment.getVariable())
|
||||
.getName()
|
||||
.toString(),
|
||||
AssignmentTree::getExpression))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* POJO containing data collected using {@link TestNGScanner} for use in {@link
|
||||
* TestNGJUnitMigration}.
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class TestNgMetadata {
|
||||
abstract ClassTree getClassTree();
|
||||
|
||||
abstract Optional<AnnotationMetadata> getClassLevelAnnotationMetadata();
|
||||
|
||||
abstract ImmutableMap<MethodTree, AnnotationMetadata> getMethodAnnotations();
|
||||
|
||||
abstract ImmutableMap<MethodTree, SetupTeardownType> getSetupTeardown();
|
||||
|
||||
/**
|
||||
* Retrieve the tests that can be migrated.
|
||||
*
|
||||
* @return An {@link ImmutableMap} with mapping {@code DataProvider}'s name to its respective
|
||||
* metadata.
|
||||
*/
|
||||
public abstract ImmutableMap<String, DataProviderMetadata> getDataProviderMetadata();
|
||||
|
||||
final ImmutableList<DataProviderMetadata> getDataProvidersInUse() {
|
||||
return getDataProviderMetadata().entrySet().stream()
|
||||
.filter(
|
||||
entry ->
|
||||
getAnnotations().stream()
|
||||
.anyMatch(
|
||||
annotation -> {
|
||||
ExpressionTree dataProviderNameExpression =
|
||||
annotation.getAttributes().get("dataProvider");
|
||||
if (dataProviderNameExpression == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ASTHelpers.constValue(dataProviderNameExpression, String.class)
|
||||
.equals(entry.getKey());
|
||||
}))
|
||||
.map(Map.Entry::getValue)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
final ImmutableSet<AnnotationMetadata> getAnnotations() {
|
||||
return ImmutableSet.copyOf(getMethodAnnotations().values());
|
||||
}
|
||||
|
||||
final Optional<AnnotationMetadata> getAnnotation(MethodTree methodTree) {
|
||||
return Optional.ofNullable(getMethodAnnotations().get(methodTree));
|
||||
}
|
||||
|
||||
static Builder builder() {
|
||||
return new AutoValue_TestNgMetadata.Builder();
|
||||
}
|
||||
|
||||
@AutoValue.Builder
|
||||
abstract static class Builder {
|
||||
abstract ImmutableMap.Builder<MethodTree, AnnotationMetadata> methodAnnotationsBuilder();
|
||||
|
||||
abstract ImmutableMap.Builder<MethodTree, SetupTeardownType> setupTeardownBuilder();
|
||||
|
||||
abstract ImmutableMap.Builder<String, DataProviderMetadata> dataProviderMetadataBuilder();
|
||||
|
||||
abstract Builder setClassTree(ClassTree value);
|
||||
|
||||
abstract Optional<AnnotationMetadata> getClassLevelAnnotationMetadata();
|
||||
|
||||
abstract Builder setClassLevelAnnotationMetadata(AnnotationMetadata value);
|
||||
|
||||
abstract Builder setMethodAnnotations(ImmutableMap<MethodTree, AnnotationMetadata> value);
|
||||
|
||||
abstract Builder setSetupTeardown(ImmutableMap<MethodTree, SetupTeardownType> value);
|
||||
|
||||
abstract Builder setDataProviderMetadata(ImmutableMap<String, DataProviderMetadata> value);
|
||||
|
||||
abstract TestNgMetadata build();
|
||||
}
|
||||
|
||||
/**
|
||||
* POJO containing data for a specific {@code Test} annotation for use in {@link
|
||||
* TestNGJUnitMigration}.
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract static class AnnotationMetadata {
|
||||
/**
|
||||
* Get the {@link AnnotationTree} of this metadata instance.
|
||||
*
|
||||
* @return the annotation tree this metadata contains information on.
|
||||
*/
|
||||
public abstract AnnotationTree getAnnotationTree();
|
||||
|
||||
/**
|
||||
* A mapping for all attributes in the annotation to their value.
|
||||
*
|
||||
* @return an {@link ImmutableMap} mapping each annotation attribute to their respective value.
|
||||
*/
|
||||
public abstract ImmutableMap<String, ExpressionTree> getAttributes();
|
||||
|
||||
/**
|
||||
* Instantiate a new {@link AnnotationMetadata}.
|
||||
*
|
||||
* @param annotationTree The annotation tree.
|
||||
* @param attributes The attributes in that annotation tree.
|
||||
* @return The new {@link AnnotationMetadata} instance.
|
||||
*/
|
||||
public static AnnotationMetadata create(
|
||||
AnnotationTree annotationTree, ImmutableMap<String, ExpressionTree> attributes) {
|
||||
return new AutoValue_TestNgMetadata_AnnotationMetadata(annotationTree, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker" /* Matcher instances are final. */)
|
||||
public enum SetupTeardownType {
|
||||
// XXX: Consider using `@BeforeAll` to more accurately preserve behavior. However, note that it
|
||||
// requires a static method and therefore may introduce breaking changes.
|
||||
BEFORE_TEST(
|
||||
"org.testng.annotations.BeforeTest",
|
||||
"org.junit.jupiter.api.BeforeEach",
|
||||
/* requiresStaticMethod= */ false),
|
||||
BEFORE_CLASS(
|
||||
"org.testng.annotations.BeforeClass",
|
||||
"org.junit.jupiter.api.BeforeAll",
|
||||
/* requiresStaticMethod= */ true),
|
||||
BEFORE_METHOD(
|
||||
"org.testng.annotations.BeforeMethod",
|
||||
"org.junit.jupiter.api.BeforeEach",
|
||||
/* requiresStaticMethod= */ false),
|
||||
// XXX: Consider using `@AfterAll` to more accurately preserve behavior. However, note that it
|
||||
// requires a static method and therefore may introduce breaking changes.
|
||||
AFTER_TEST(
|
||||
"org.testng.annotations.AfterTest",
|
||||
"org.junit.jupiter.api.AfterEach",
|
||||
/* requiresStaticMethod= */ false),
|
||||
AFTER_CLASS(
|
||||
"org.testng.annotations.AfterClass",
|
||||
"org.junit.jupiter.api.AfterAll",
|
||||
/* requiresStaticMethod= */ true),
|
||||
AFTER_METHOD(
|
||||
"org.testng.annotations.AfterMethod",
|
||||
"org.junit.jupiter.api.AfterEach",
|
||||
/* requiresStaticMethod= */ false);
|
||||
|
||||
private final Matcher<AnnotationTree> annotationMatcher;
|
||||
private final Matcher<MethodTree> methodTreeMatcher;
|
||||
private final String junitAnnotationClass;
|
||||
private final boolean requiresStaticMethod;
|
||||
|
||||
SetupTeardownType(
|
||||
String testNgAnnotationClass, String junitAnnotationClass, boolean requiresStaticMethod) {
|
||||
annotationMatcher = isType(testNgAnnotationClass);
|
||||
methodTreeMatcher = hasAnnotation(testNgAnnotationClass);
|
||||
this.junitAnnotationClass = junitAnnotationClass;
|
||||
this.requiresStaticMethod = requiresStaticMethod;
|
||||
}
|
||||
|
||||
static Optional<SetupTeardownType> matchType(MethodTree methodTree, VisitorState state) {
|
||||
return Arrays.stream(values())
|
||||
.filter(v -> v.methodTreeMatcher.matches(methodTree, state))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
Matcher<AnnotationTree> getAnnotationMatcher() {
|
||||
return annotationMatcher;
|
||||
}
|
||||
|
||||
String getJunitAnnotationClass() {
|
||||
return junitAnnotationClass;
|
||||
}
|
||||
|
||||
// XXX: Improve method name.
|
||||
boolean requiresStaticMethod() {
|
||||
return requiresStaticMethod;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains data for a {@code DataProvider} annotation for use in {@link TestNGJUnitMigration}.
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract static class DataProviderMetadata {
|
||||
abstract MethodTree getMethodTree();
|
||||
|
||||
abstract String getName();
|
||||
|
||||
/**
|
||||
* Instantiate a new {@link DataProviderMetadata} instance.
|
||||
*
|
||||
* @param methodTree The value factory method tree.
|
||||
* @return A new {@link DataProviderMetadata} instance.
|
||||
*/
|
||||
public static DataProviderMetadata create(MethodTree methodTree) {
|
||||
return new AutoValue_TestNgMetadata_DataProviderMetadata(
|
||||
methodTree, methodTree.getName().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import tech.picnic.errorprone.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link AttributeMigrator} that migrates the {@code org.testng.annotations.Test#timeOut}
|
||||
* attribute. The TestNG {@code org.testng.annotations.Test#timeOut} attribute is always in
|
||||
* milliseconds, the JUnit variant {@code @Timeout} takes a value in seconds by default, hence we
|
||||
* add the {@link java.util.concurrent.TimeUnit#MILLISECONDS} attribute.
|
||||
*/
|
||||
@Immutable
|
||||
final class TimeOutAttributeMigrator implements AttributeMigrator {
|
||||
@Override
|
||||
public Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata metadata,
|
||||
TestNgMetadata.AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
VisitorState state) {
|
||||
return Optional.ofNullable(annotation.getAttributes().get("timeOut"))
|
||||
.map(
|
||||
timeOut ->
|
||||
SuggestedFix.builder()
|
||||
.addImport("org.junit.jupiter.api.Timeout")
|
||||
.addStaticImport(TimeUnit.class.getCanonicalName() + ".MILLISECONDS")
|
||||
.prefixWith(
|
||||
methodTree,
|
||||
String.format(
|
||||
"@Timeout(value = %s, unit = MILLISECONDS)%n",
|
||||
SourceCode.treeToString(timeOut, state)))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link AttributeMigrator} that leaves a comment for attributes that aren't supported in the
|
||||
* migration.
|
||||
*/
|
||||
@Immutable
|
||||
final class UnsupportedAttributeMigrator {
|
||||
private UnsupportedAttributeMigrator() {}
|
||||
|
||||
static Optional<SuggestedFix> migrate(
|
||||
TestNgMetadata.AnnotationMetadata annotation,
|
||||
MethodTree methodTree,
|
||||
String attributeName,
|
||||
VisitorState state) {
|
||||
return Optional.ofNullable(annotation.getAttributes().get(attributeName))
|
||||
.map(
|
||||
value ->
|
||||
SuggestedFix.prefixWith(
|
||||
methodTree,
|
||||
String.format(
|
||||
"// XXX: Attribute `%s` is not supported, value: `%s`%n",
|
||||
attributeName, SourceCode.treeToString(value, state))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/** TestNG to JUnit migration using Error-Prone. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
@@ -0,0 +1,127 @@
|
||||
package tech.picnic.errorprone.testngjunit.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.testng.Assert;
|
||||
|
||||
/** Refaster rules to replace TestNG assertions with JUnit equivalents. */
|
||||
@SuppressWarnings("StaticImport")
|
||||
final class TestNgAssertionsToJUnitRules {
|
||||
private TestNgAssertionsToJUnitRules() {}
|
||||
|
||||
@SuppressWarnings("AssertEqual")
|
||||
static final class AssertEquals {
|
||||
@BeforeTemplate
|
||||
void before(Object expected, Object actual) {
|
||||
Assert.assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(Object expected, Object actual) {
|
||||
Assertions.assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("AssertEqualWithMessage")
|
||||
static final class AssertEqualsMessage {
|
||||
@BeforeTemplate
|
||||
void before(Object expected, Object actual, String message) {
|
||||
Assert.assertEquals(actual, expected, message);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(Object expected, Object actual, String message) {
|
||||
Assertions.assertEquals(expected, actual, message);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("AssertUnequal")
|
||||
static final class AssertNotEquals {
|
||||
@BeforeTemplate
|
||||
void before(Object expected, Object actual) {
|
||||
Assert.assertNotEquals(actual, expected);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(Object expected, Object actual) {
|
||||
Assertions.assertNotEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("AssertUnequalWithMessage")
|
||||
static final class AssertNotEqualsMessage {
|
||||
@BeforeTemplate
|
||||
void before(Object expected, Object actual, String message) {
|
||||
Assert.assertNotEquals(actual, expected, message);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(Object expected, Object actual, String message) {
|
||||
Assertions.assertNotEquals(expected, actual, message);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"AssertFalse", "AssertThatIsFalse"})
|
||||
static final class AssertFalseCondition {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition) {
|
||||
Assert.assertFalse(condition);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition) {
|
||||
Assertions.assertFalse(condition);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"AssertFalseWithMessage", "AssertThatWithFailMessageStringIsFalse"})
|
||||
static final class AssertFalseConditionMessage {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition, String message) {
|
||||
Assert.assertFalse(condition, message);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition, String message) {
|
||||
Assertions.assertFalse(condition, message);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"AssertThatIsTrue", "AssertTrue"})
|
||||
static final class AssertTrueCondition {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition) {
|
||||
Assert.assertTrue(condition);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition) {
|
||||
Assertions.assertTrue(condition);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"AssertThatWithFailMessageStringIsTrue", "AssertTrueWithMessage"})
|
||||
static final class AssertTrueConditionMessage {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition, String message) {
|
||||
Assert.assertTrue(condition, message);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition, String message) {
|
||||
Assertions.assertTrue(condition, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/** Picnic Refaster rules. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package tech.picnic.errorprone.testngjunit.refasterrules;
|
||||
@@ -0,0 +1,27 @@
|
||||
package tech.picnic.errorprone.util;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.sun.source.tree.Tree;
|
||||
|
||||
/**
|
||||
* A collection of Error Prone utility methods for dealing with the source code representation of
|
||||
* AST nodes.
|
||||
*/
|
||||
// XXX: This is a duplicate of `error-prone-contrib`s `SourceCode`, improve this.
|
||||
public final class SourceCode {
|
||||
private SourceCode() {}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the given {@link Tree}, preferring the original source code
|
||||
* (if available) over its prettified representation.
|
||||
*
|
||||
* @param tree The AST node of interest.
|
||||
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
|
||||
* found.
|
||||
* @return A non-{@code null} string.
|
||||
*/
|
||||
public static String treeToString(Tree tree, VisitorState state) {
|
||||
String src = state.getSourceForNode(tree);
|
||||
return src != null ? src : tree.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class TestNGJUnitMigrationTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(TestNGJUnitMigration.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"import org.testng.annotations.DataProvider;",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"@Test",
|
||||
"public class A {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void classLevelAnnotation() {}",
|
||||
"",
|
||||
" public static void staticNotATest() {}",
|
||||
"",
|
||||
" private void notATest() {}",
|
||||
"",
|
||||
" @Test(description = \"bar\")",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void methodAnnotation() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" public static class B {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void nestedClass() {}",
|
||||
" }",
|
||||
"",
|
||||
" @Test(dataProvider = \"\")",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void dataProviderEmptyString() {}",
|
||||
"",
|
||||
" @Test(dataProvider = \"dataProviderTestCases\")",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void dataProvider(int foo) {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static Object[][] dataProviderTestCases() {",
|
||||
" return new Object[][] {{1}, {2}};",
|
||||
" }",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" private static Object[][] unusedDataProvider() {",
|
||||
" return new Object[][] {{1}, {2}};",
|
||||
" }",
|
||||
"",
|
||||
" @Test(dataProvider = \"notMigratableDataProviderTestCases\")",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void notMigratableDataProvider(int foo) {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" private static Object[][] notMigratableDataProviderTestCases() {",
|
||||
" return Stream.of(1, 2, 3).map(i -> new Object[] {i}).toArray(Object[][]::new);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationConservativeMode() {
|
||||
CompilationTestHelper.newInstance(TestNGJUnitMigration.class, getClass())
|
||||
.setArgs("-XepOpt:TestNGJUnitMigration:ConservativeMode=true")
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"import org.testng.annotations.DataProvider;",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"@Test",
|
||||
"public class A {",
|
||||
" public void classLevelAnnotation() {}",
|
||||
"",
|
||||
" @Test(description = \"bar\")",
|
||||
" public void methodAnnotation() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" public static class B {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void nestedClass() {}",
|
||||
" }",
|
||||
"",
|
||||
" @Test(dataProvider = \"notMigratableDataProviderTestCases\")",
|
||||
" public void notMigratableDataProvider(int foo) {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" private static Object[][] notMigratableDataProviderTestCases() {",
|
||||
" return Stream.of(1, 2, 3).map(i -> new Object[] {i}).toArray(Object[][]::new);",
|
||||
" }",
|
||||
"}")
|
||||
.addSourceLines(
|
||||
"B.java",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"@Test",
|
||||
"public class B {",
|
||||
" public void classLevelAnnotation() {}",
|
||||
"",
|
||||
" @Test(description = \"bar\")",
|
||||
" public void methodAnnotation() {}",
|
||||
"",
|
||||
" @Test(testName = \"unsupportedAttribute\")",
|
||||
" public void unsupportedAttribute() {}",
|
||||
"",
|
||||
" @Test(testName = \"unsupportedAttribute\", suiteName = \"unsupportedAttribute\")",
|
||||
" public void multipleUnsupportedAttributes() {}",
|
||||
"}")
|
||||
.addSourceLines(
|
||||
"C.java",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"@Test",
|
||||
"public class C {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void classLevelAnnotation() {}",
|
||||
"",
|
||||
" @Test(description = \"bar\")",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" public void methodAnnotation() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(TestNGJUnitMigration.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static org.testng.Assert.*;",
|
||||
"",
|
||||
"import org.testng.annotations.AfterClass;",
|
||||
"import org.testng.annotations.AfterMethod;",
|
||||
"import org.testng.annotations.AfterTest;",
|
||||
"import org.testng.annotations.BeforeClass;",
|
||||
"import org.testng.annotations.BeforeMethod;",
|
||||
"import org.testng.annotations.BeforeTest;",
|
||||
"import org.testng.annotations.DataProvider;",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"@Test",
|
||||
"class A {",
|
||||
" @BeforeTest",
|
||||
" private void setupTest() {}",
|
||||
"",
|
||||
" @BeforeClass",
|
||||
" private void setupClass() {}",
|
||||
"",
|
||||
" @BeforeMethod",
|
||||
" private void setup() {}",
|
||||
"",
|
||||
" @AfterTest",
|
||||
" private void teardownTest() {}",
|
||||
"",
|
||||
" @AfterClass",
|
||||
" private void teardownClass() {}",
|
||||
"",
|
||||
" @AfterMethod",
|
||||
" private void teardown() {}",
|
||||
"",
|
||||
" public void classLevelAnnotation() {}",
|
||||
"",
|
||||
" @Test(priority = 1, timeOut = 500, description = \"unit\")",
|
||||
" public void priorityTimeOutAndDescription() {}",
|
||||
"",
|
||||
" @Test(testName = \"unsupportedAttribute\")",
|
||||
" public void unsupportedAttribute() {}",
|
||||
"",
|
||||
" @Test(testName = \"unsupportedAttribute\", suiteName = \"unsupportedAttribute\")",
|
||||
" public void multipleUnsupportedAttributes() {}",
|
||||
"",
|
||||
" @Test(dataProvider = \"dataProviderTestCases\")",
|
||||
" public void dataProvider(String string, int number) {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" private Object[][] dataProviderTestCases() {",
|
||||
" int[] values = new int[] {1, 2, 3};",
|
||||
" return new Object[][] {",
|
||||
" {\"1\", values[0], getClass()},",
|
||||
" {\"2\", values[1], this.getClass()},",
|
||||
" {\"3\", /* inline comment */ values[2], getClass()}",
|
||||
" };",
|
||||
" }",
|
||||
"",
|
||||
" @Test(dataProvider = \"dataProviderFieldReturnValueTestCases\")",
|
||||
" public void dataProviderFieldReturnValue(int foo, int bar) {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" private Object[][] dataProviderFieldReturnValueTestCases() {",
|
||||
" Object[][] foo = new Object[][] {{1, 2}};",
|
||||
" return foo;",
|
||||
" }",
|
||||
"",
|
||||
" @Test(dataProvider = \"dataProviderThrowsTestCases\")",
|
||||
" public void dataProviderThrows(String foo, int bar) {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" private Object[][] dataProviderThrowsTestCases() throws RuntimeException {",
|
||||
" return new Object[][] {",
|
||||
" {\"1\", 0},",
|
||||
" };",
|
||||
" }",
|
||||
"",
|
||||
" @Test(expectedExceptions = RuntimeException.class)",
|
||||
" public void singleExpectedException() {",
|
||||
" throw new RuntimeException(\"foo\");",
|
||||
" }",
|
||||
"",
|
||||
" @Test(expectedExceptions = {RuntimeException.class})",
|
||||
" public void singleExpectedExceptionArray() {",
|
||||
" throw new RuntimeException(\"foo\");",
|
||||
" }",
|
||||
"",
|
||||
" @Test(expectedExceptions = {})",
|
||||
" public void emptyExpectedExceptions() {}",
|
||||
"",
|
||||
" @Test(expectedExceptions = {IllegalArgumentException.class, RuntimeException.class})",
|
||||
" public void multipleExpectedExceptions() {",
|
||||
" throw new RuntimeException(\"foo\");",
|
||||
" }",
|
||||
"",
|
||||
" @Test(enabled = false)",
|
||||
" public void disabledTest() {}",
|
||||
"",
|
||||
" @Test(enabled = true)",
|
||||
" public void enabledTest() {}",
|
||||
"",
|
||||
" @Test(groups = \"foo\")",
|
||||
" public void groupsTest() {}",
|
||||
"",
|
||||
" @Test(groups = {\"foo\", \"bar\"})",
|
||||
" public void multipleGroupsTest() {}",
|
||||
"",
|
||||
" @Test(groups = {})",
|
||||
" public void emptyGroupsTest() {}",
|
||||
"",
|
||||
" @Test(groups = \"\")",
|
||||
" public void invalidGroupsNameTest() {}",
|
||||
"",
|
||||
" @Test(groups = \" whitespace \")",
|
||||
" public void whitespaceGroupsNameTest() {}",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static java.util.concurrent.TimeUnit.MILLISECONDS;",
|
||||
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
|
||||
"",
|
||||
"import java.util.stream.Stream;",
|
||||
"import org.junit.jupiter.api.Disabled;",
|
||||
"import org.junit.jupiter.api.DisplayName;",
|
||||
"import org.junit.jupiter.api.MethodOrderer;",
|
||||
"import org.junit.jupiter.api.Order;",
|
||||
"import org.junit.jupiter.api.Tag;",
|
||||
"import org.junit.jupiter.api.TestMethodOrder;",
|
||||
"import org.junit.jupiter.api.Timeout;",
|
||||
"import org.junit.jupiter.params.ParameterizedTest;",
|
||||
"import org.junit.jupiter.params.provider.Arguments;",
|
||||
"import org.junit.jupiter.params.provider.MethodSource;",
|
||||
"import org.testng.annotations.AfterClass;",
|
||||
"import org.testng.annotations.AfterMethod;",
|
||||
"import org.testng.annotations.AfterTest;",
|
||||
"import org.testng.annotations.BeforeClass;",
|
||||
"import org.testng.annotations.BeforeMethod;",
|
||||
"import org.testng.annotations.BeforeTest;",
|
||||
"import org.testng.annotations.DataProvider;",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"@TestMethodOrder(MethodOrderer.OrderAnnotation.class)",
|
||||
"class A {",
|
||||
" @org.junit.jupiter.api.BeforeEach",
|
||||
" private void setupTest() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.BeforeAll",
|
||||
" private static void setupClass() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.BeforeEach",
|
||||
" private void setup() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.AfterEach",
|
||||
" private void teardownTest() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.AfterAll",
|
||||
" private static void teardownClass() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.AfterEach",
|
||||
" private void teardown() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void classLevelAnnotation() {}",
|
||||
"",
|
||||
" @Order(1)",
|
||||
" @Timeout(value = 500, unit = MILLISECONDS)",
|
||||
" @DisplayName(\"unit\")",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void priorityTimeOutAndDescription() {}",
|
||||
"",
|
||||
" // XXX: Attribute `testName` is not supported, value: `\"unsupportedAttribute\"`",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void unsupportedAttribute() {}",
|
||||
"",
|
||||
" // XXX: Attribute `testName` is not supported, value: `\"unsupportedAttribute\"`",
|
||||
" // XXX: Attribute `suiteName` is not supported, value: `\"unsupportedAttribute\"`",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void multipleUnsupportedAttributes() {}",
|
||||
"",
|
||||
" @ParameterizedTest",
|
||||
" @MethodSource(\"dataProviderTestCases\")",
|
||||
" public void dataProvider(String string, int number) {}",
|
||||
"",
|
||||
" private static Stream<Arguments> dataProviderTestCases() {",
|
||||
" int[] values = new int[] {1, 2, 3};",
|
||||
" return Stream.of(",
|
||||
" arguments(\"1\", values[0], A.class),",
|
||||
" arguments(\"2\", values[1], A.class),",
|
||||
" arguments(\"3\", /* inline comment */ values[2], A.class));",
|
||||
" }",
|
||||
"",
|
||||
" // XXX: Attribute `dataProvider` is not supported, value:",
|
||||
" // `\"dataProviderFieldReturnValueTestCases\"`",
|
||||
"",
|
||||
" public void dataProviderFieldReturnValue(int foo, int bar) {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" private Object[][] dataProviderFieldReturnValueTestCases() {",
|
||||
" Object[][] foo = new Object[][] {{1, 2}};",
|
||||
" return foo;",
|
||||
" }",
|
||||
"",
|
||||
" @ParameterizedTest",
|
||||
" @MethodSource(\"dataProviderThrowsTestCases\")",
|
||||
" public void dataProviderThrows(String foo, int bar) {}",
|
||||
"",
|
||||
" private static Stream<Arguments> dataProviderThrowsTestCases() throws RuntimeException {",
|
||||
" return Stream.of(arguments(\"1\", 0));",
|
||||
" }",
|
||||
"",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void singleExpectedException() {",
|
||||
" org.junit.jupiter.api.Assertions.assertThrows(",
|
||||
" RuntimeException.class,",
|
||||
" () -> {",
|
||||
" throw new RuntimeException(\"foo\");",
|
||||
" });",
|
||||
" }",
|
||||
"",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void singleExpectedExceptionArray() {",
|
||||
" org.junit.jupiter.api.Assertions.assertThrows(",
|
||||
" RuntimeException.class,",
|
||||
" () -> {",
|
||||
" throw new RuntimeException(\"foo\");",
|
||||
" });",
|
||||
" }",
|
||||
"",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void emptyExpectedExceptions() {}",
|
||||
"",
|
||||
" // XXX: Removed handling of `RuntimeException.class` because this migration doesn't support",
|
||||
" // XXX: multiple expected exceptions.",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void multipleExpectedExceptions() {",
|
||||
" org.junit.jupiter.api.Assertions.assertThrows(",
|
||||
" IllegalArgumentException.class,",
|
||||
" () -> {",
|
||||
" throw new RuntimeException(\"foo\");",
|
||||
" });",
|
||||
" }",
|
||||
"",
|
||||
" @Disabled",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void disabledTest() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void enabledTest() {}",
|
||||
"",
|
||||
" @Tag(\"foo\")",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void groupsTest() {}",
|
||||
"",
|
||||
" @Tag(\"foo\")",
|
||||
" @Tag(\"bar\")",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void multipleGroupsTest() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void emptyGroupsTest() {}",
|
||||
"",
|
||||
" // XXX: Attribute `groups` is not supported, value: `\"\"`",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void invalidGroupsNameTest() {}",
|
||||
"",
|
||||
" @Tag(\"whitespace\")",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" public void whitespaceGroupsNameTest() {}",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class TestNGMatchersTest {
|
||||
@Test
|
||||
void matches() {
|
||||
CompilationTestHelper.newInstance(TestNGMatchersTestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.testng.annotations.DataProvider;",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"// BUG: Diagnostic contains: TestNG annotation",
|
||||
"@Test",
|
||||
"class A {",
|
||||
" // BUG: Diagnostic contains: TestNG annotation",
|
||||
" @Test",
|
||||
" void basic() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: TestNG annotation",
|
||||
" @Test(dataProvider = \"dataProviderTestCases\")",
|
||||
" void withDataProvider() {}",
|
||||
"",
|
||||
" @DataProvider",
|
||||
" // BUG: Diagnostic contains: TestNG value factory method",
|
||||
" private static Object[][] dataProviderTestCases() {",
|
||||
" return new Object[][] {};",
|
||||
" }",
|
||||
"",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" void junitTest() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
/** A {@link BugChecker} used to report TestNG annotations as errors for testing purposes. */
|
||||
@BugPattern(summary = "Interacts with `TestNGMatchers` for testing purposes", severity = ERROR)
|
||||
public static final class TestNGMatchersTestChecker extends BugChecker
|
||||
implements MethodTreeMatcher, AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree annotationTree, VisitorState visitorState) {
|
||||
return TestNGMatchers.TESTNG_TEST_ANNOTATION.matches(annotationTree, visitorState)
|
||||
? buildDescription(annotationTree).setMessage("TestNG annotation").build()
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree methodTree, VisitorState visitorState) {
|
||||
return TestNGMatchers.TESTNG_VALUE_FACTORY_METHOD.matches(methodTree, visitorState)
|
||||
? buildDescription(methodTree).setMessage("TestNG value factory method").build()
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package tech.picnic.errorprone.testngjunit;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static java.util.function.Predicate.isEqual;
|
||||
import static java.util.function.Predicate.not;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Name;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tech.picnic.errorprone.testngjunit.TestNgMetadata.AnnotationMetadata;
|
||||
|
||||
final class TestNGScannerTest {
|
||||
@Test
|
||||
void classLevelAndMethodLevel() {
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.testng.annotations.Test;",
|
||||
"",
|
||||
"// BUG: Diagnostic contains: Class: A attributes: {}",
|
||||
"@Test",
|
||||
"class A {",
|
||||
"",
|
||||
" public void inferClassLevelAnnotation() {}",
|
||||
"",
|
||||
" void packagePrivateNotATest() {}",
|
||||
"",
|
||||
" private void privateNotATest() {}",
|
||||
"",
|
||||
" static void notATest() {}",
|
||||
"",
|
||||
" public static void staticNotATest() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: Class: A attributes: {}",
|
||||
" @Test",
|
||||
" public void localAnnotation() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: Class: A attributes: {description=\"foo\"}",
|
||||
" @Test(description = \"foo\")",
|
||||
" public void singleArgument() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: Class: A attributes: {priority=1, description=\"foo\"}",
|
||||
" @Test(priority = 1, description = \"foo\")",
|
||||
" public void multipleArguments() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: Class: A attributes: {dataProvider=\"dataProviderTestCases\"}",
|
||||
" @Test(dataProvider = \"dataProviderTestCases\")",
|
||||
" public void dataProvider() {}",
|
||||
"",
|
||||
" @SuppressWarnings(\"onlyMatchTestNGAnnotations\")",
|
||||
" // BUG: Diagnostic contains: Class: B attributes: {description=\"nested\"}",
|
||||
" @Test(description = \"nested\")",
|
||||
" class B {",
|
||||
" public void nestedTest() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: Class: B attributes: {priority=1}",
|
||||
" @Test(priority = 1)",
|
||||
" public void nestedTestWithArguments() {}",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
// XXX: Here we need to add some edge cases for the DataProvider probably?
|
||||
@Test
|
||||
void dataProvider() {
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"import org.testng.annotations.DataProvider;",
|
||||
"",
|
||||
"class A {",
|
||||
" @DataProvider",
|
||||
" // BUG: Diagnostic contains: Class: A DataProvider: dataProviderTestCases",
|
||||
" private static Object[][] dataProviderTestCases() {",
|
||||
" return new Object[][] {{1}, {2}};",
|
||||
" }",
|
||||
"",
|
||||
" private static Object[][] notMigratableDataProviderTestCases() {",
|
||||
" return Stream.of(1, 2, 3).map(i -> new Object[] {i}).toArray(Object[][]::new);",
|
||||
" }",
|
||||
"",
|
||||
" private static Object[][] notMigratableDataProvider2TestCases() {",
|
||||
" Object[][] testCases = new Object[][] {{1}, {2}};",
|
||||
" return testCases;",
|
||||
" }",
|
||||
"",
|
||||
" private static Object[] notMigratableDataProvider3TestCases() {",
|
||||
" return new Object[] {new Object[] {1}, new Object[] {2}};",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void junitTestClass() {
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" private void foo() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalClass() {
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass())
|
||||
.addSourceLines("A.java", "class A {", " private void foo() {}", "}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void teardownAndSetupMethods() {
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.testng.annotations.AfterClass;",
|
||||
"import org.testng.annotations.AfterMethod;",
|
||||
"import org.testng.annotations.BeforeClass;",
|
||||
"import org.testng.annotations.BeforeMethod;",
|
||||
"",
|
||||
"class A {",
|
||||
" @BeforeClass",
|
||||
" // BUG: Diagnostic contains: Class: A SetupTearDown: BEFORE_CLASS",
|
||||
" private static void beforeClass() {}",
|
||||
"",
|
||||
" @BeforeMethod",
|
||||
" // BUG: Diagnostic contains: Class: A SetupTearDown: BEFORE_METHOD",
|
||||
" private void beforeMethod() {}",
|
||||
"",
|
||||
" @AfterClass",
|
||||
" // BUG: Diagnostic contains: Class: A SetupTearDown: AFTER_CLASS",
|
||||
" private static void afterClass() {}",
|
||||
"",
|
||||
" @AfterMethod",
|
||||
" // BUG: Diagnostic contains: Class: A SetupTearDown: AFTER_METHOD",
|
||||
" private void afterMethod() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags classes with a diagnostics message that indicates, whether a
|
||||
* TestNG element was collected.
|
||||
*/
|
||||
@BugPattern(severity = ERROR, summary = "Interacts with `TestNGScanner` for testing purposes")
|
||||
public static final class TestChecker extends BugChecker
|
||||
implements CompilationUnitTreeMatcher, ClassTreeMatcher, MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
// XXX: find better way to do this
|
||||
private ImmutableMap<ClassTree, TestNgMetadata> classMetaData = ImmutableMap.of();
|
||||
|
||||
@Override
|
||||
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
|
||||
TestNGScanner scanner = new TestNGScanner(state);
|
||||
classMetaData = scanner.collectMetadataForClasses(tree);
|
||||
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
Optional.ofNullable(classMetaData.get(tree))
|
||||
.flatMap(TestNgMetadata::getClassLevelAnnotationMetadata)
|
||||
.ifPresent(annotation -> reportAnnotationMessage(tree, annotation, state));
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
ClassTree classTree = state.findEnclosing(ClassTree.class);
|
||||
Optional<TestNgMetadata> metadata = Optional.ofNullable(classTree).map(classMetaData::get);
|
||||
|
||||
if (metadata.isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
reportClassLevelAnnotation(classTree, metadata.orElseThrow(), state);
|
||||
reportTestMethods(tree, classTree, metadata.orElseThrow(), state);
|
||||
reportDataProviderMethods(tree, classTree, metadata.orElseThrow(), state);
|
||||
reportSetupTeardownMethods(tree, classTree, metadata.orElseThrow(), state);
|
||||
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private void reportClassLevelAnnotation(
|
||||
ClassTree classTree, TestNgMetadata metadata, VisitorState state) {
|
||||
metadata
|
||||
.getClassLevelAnnotationMetadata()
|
||||
.ifPresent(annotation -> reportAnnotationMessage(classTree, annotation, state));
|
||||
}
|
||||
|
||||
private void reportTestMethods(
|
||||
MethodTree tree, ClassTree classTree, TestNgMetadata metadata, VisitorState state) {
|
||||
metadata
|
||||
.getClassLevelAnnotationMetadata()
|
||||
.filter(not(isEqual(metadata.getMethodAnnotations().get(tree))))
|
||||
.map(unused -> metadata.getMethodAnnotations().get(tree))
|
||||
.ifPresent(annotation -> reportAnnotationMessage(classTree, annotation, state));
|
||||
}
|
||||
|
||||
private void reportSetupTeardownMethods(
|
||||
MethodTree tree, ClassTree classTree, TestNgMetadata metadata, VisitorState state) {
|
||||
metadata.getSetupTeardown().entrySet().stream()
|
||||
.filter(entry -> entry.getKey().equals(tree))
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
entry ->
|
||||
reportMethodMessage(
|
||||
classTree.getSimpleName(),
|
||||
"SetupTearDown",
|
||||
entry.getValue().name(),
|
||||
entry.getKey(),
|
||||
state));
|
||||
}
|
||||
|
||||
private void reportDataProviderMethods(
|
||||
MethodTree tree, ClassTree classTree, TestNgMetadata metadata, VisitorState state) {
|
||||
metadata.getDataProviderMetadata().entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getMethodTree().equals(tree))
|
||||
.findFirst()
|
||||
.map(Map.Entry::getValue)
|
||||
.ifPresent(
|
||||
dataProvider -> {
|
||||
reportMethodMessage(
|
||||
classTree.getSimpleName(),
|
||||
"DataProvider",
|
||||
dataProvider.getName(),
|
||||
dataProvider.getMethodTree(),
|
||||
state);
|
||||
});
|
||||
}
|
||||
|
||||
private void reportAnnotationMessage(
|
||||
ClassTree classTree, AnnotationMetadata annotation, VisitorState state) {
|
||||
state.reportMatch(
|
||||
buildDescription(annotation.getAnnotationTree())
|
||||
.setMessage(createMetaDataMessage(classTree, annotation))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void reportMethodMessage(
|
||||
Name className, String message, String name, Tree tree, VisitorState state) {
|
||||
state.reportMatch(
|
||||
buildDescription(tree)
|
||||
.setMessage(String.format("Class: %s %s: %s", className, message, name))
|
||||
.build());
|
||||
}
|
||||
|
||||
private static String createMetaDataMessage(
|
||||
ClassTree classTree, AnnotationMetadata annotationMetadata) {
|
||||
return String.format(
|
||||
"Class: %s attributes: %s",
|
||||
classTree.getSimpleName(), annotationMetadata.getAttributes());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user