mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
feat(run): recognize TestNG classes annotated with @Test (#1403)
TestNG's class-level @Test makes the class's public void methods tests; the scanner now recognizes them (excluding @Before*/@After*/@DataProvider/@Factory). Test results locate TestNG failures past org.testng frames and parse TestNG's comparison message. Refs #1368
This commit is contained in:
@@ -94,6 +94,31 @@ describe("scanJvmTargets (Java)", () => {
|
||||
[16, "test", "a.b.OrderTest$Fixture", "inner", false],
|
||||
]);
|
||||
});
|
||||
|
||||
it("treats the public void methods of a TestNG class-level @Test as tests", () => {
|
||||
const src = [
|
||||
"package a.b;", // 0
|
||||
"import org.testng.annotations.*;",
|
||||
'@Test(groups = "unit")',
|
||||
"public class PriceTest {", // 3
|
||||
" @BeforeMethod public void setUp() {}",
|
||||
" public void rounds() {}", // 5
|
||||
' @Test(dataProvider = "rows")',
|
||||
" public void parses(String row) {}", // 7
|
||||
" @DataProvider public Object[][] rows() { return null; }",
|
||||
' @DataProvider(name = "more") public void notATest() {}',
|
||||
" void packagePrivate() {}",
|
||||
" private void util() {}",
|
||||
" public static void shared() {}",
|
||||
" public int value() { return 1; }",
|
||||
"}",
|
||||
].join("\n");
|
||||
expect(pick(src, "src/test/java/a/b/PriceTest.java")).toEqual([
|
||||
[3, "suite", "a.b.PriceTest", null, false],
|
||||
[5, "test", "a.b.PriceTest", "rounds", false],
|
||||
[7, "test", "a.b.PriceTest", "parses", false],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("scanJvmTargets (Kotlin)", () => {
|
||||
|
||||
@@ -89,6 +89,8 @@ function tokenize(code: string): Token[] {
|
||||
}
|
||||
|
||||
const TEST_ANNOTATIONS = new Set(["Test", "ParameterizedTest", "RepeatedTest", "TestFactory", "TestTemplate"]);
|
||||
/** TestNG methods that set up or feed tests; not tests themselves under a class-level `@Test`. */
|
||||
const TESTNG_NOT_TESTS = /^(Before|After)\w*$|^(DataProvider|Factory)$/;
|
||||
const TYPE_KEYWORDS = new Set(["class", "interface", "enum", "record", "object"]);
|
||||
const NOT_TYPES = new Set(["new", "return", "throw", "else", "case", "assert", "yield", "await", "this", "super"]);
|
||||
const KOTLIN_DECL = /^(fun|val|var|class|object|interface|enum|data|sealed|abstract|open|private|public|internal|@)$/;
|
||||
@@ -246,7 +248,16 @@ function scan(src: string, relPath: string, kotlin: boolean) {
|
||||
const modifiers: string[] = [];
|
||||
for (let k = i - 1; k >= 0 && !MEMBER_END.has(tokens[k]!.text); k--) modifiers.push(tokens[k]!.text);
|
||||
const owner = top();
|
||||
if (annotations.some((a) => TEST_ANNOTATIONS.has(a)) && owner) {
|
||||
// TestNG `@Test` on a class (JUnit's is method-only): its public `void` methods are tests.
|
||||
const classLevelTest =
|
||||
!kotlin &&
|
||||
owner?.kind === "class" &&
|
||||
owner.annotations.includes("Test") &&
|
||||
prev === "void" &&
|
||||
modifiers.includes("public") &&
|
||||
!modifiers.includes("static") &&
|
||||
!annotations.some((a) => TESTNG_NOT_TESTS.test(a));
|
||||
if ((annotations.some((a) => TEST_ANNOTATIONS.has(a)) || classLevelTest) && owner) {
|
||||
const className = qualify(binaryName(frames));
|
||||
out.push({ line: tokens[i]!.line, kind: "test", className, method: name });
|
||||
for (let f = frames.length; f > 0; f--) {
|
||||
|
||||
@@ -10,6 +10,13 @@ describe("parseComparison", () => {
|
||||
expect(parseComparison(['expected: "a b"', ' but was: "a c"'])).toEqual({ expected: '"a b"', actual: '"a c"' });
|
||||
});
|
||||
|
||||
it("TestNG", () => {
|
||||
expect(parseComparison(["java.lang.AssertionError: total expected [3] but found [2]"])).toEqual({
|
||||
expected: "3",
|
||||
actual: "2",
|
||||
});
|
||||
});
|
||||
|
||||
it("kotlin.test", () => {
|
||||
expect(parseComparison(["java.lang.AssertionError: Expected <1>, actual <2>."])).toEqual({
|
||||
expected: "1",
|
||||
|
||||
@@ -21,6 +21,8 @@ export function parseComparison(output: readonly string[]): Comparison | null {
|
||||
// JUnit / opentest4j / AssertJ / Hamcrest-style: `expected: <x> but was: <y>`.
|
||||
if ((m = /expected:\s*(<[\s\S]*?>|\S[^\n]*?)\s*but was:\s*(<[\s\S]*?>|[^\n]*)/.exec(text)))
|
||||
return { expected: unwrap(m[1]!), actual: unwrap(m[2]!) };
|
||||
// TestNG `Assert.assertEquals`: `expected [x] but found [y]`.
|
||||
if ((m = /expected \[([\s\S]*?)\] but found \[([\s\S]*?)\]/.exec(text))) return { expected: m[1]!, actual: m[2]! };
|
||||
// kotlin.test: `Expected <x>, actual <y>.`
|
||||
if ((m = /Expected <([\s\S]*?)>, actual <([\s\S]*?)>/.exec(text))) return { expected: m[1]!, actual: m[2]! };
|
||||
// Rust `assert_eq!`: ` left: x` / ` right: y` (the macro doesn't say which one is expected).
|
||||
|
||||
@@ -21,7 +21,7 @@ const SUREFIRE_CLASS_RE =
|
||||
const SUREFIRE_TOTAL_RE = /^(?:\[\w+\]\s+)?Tests run: (\d+), Failures: (\d+), Errors: (\d+), Skipped: (\d+)\s*$/;
|
||||
// `at org.junit…`, also behind a class loader or module (`at app//org.junit…`, `at java.base@21/java.lang…`).
|
||||
const LIBRARY_FRAME_RE =
|
||||
/^\s*at\s+(?:\S*\/)?(?:org\.junit|junit\.|org\.opentest4j|kotlin\.|jdk\.internal|java\.|javax\.|sun\.|org\.gradle)/;
|
||||
/^\s*at\s+(?:\S*\/)?(?:org\.junit|junit\.|org\.opentest4j|org\.testng|kotlin\.|jdk\.internal|java\.|javax\.|sun\.|org\.gradle)/;
|
||||
|
||||
/** The source position of a stack frame in the project's code, null for library frames. */
|
||||
function frameLocation(line: string): TestLocation | null {
|
||||
|
||||
@@ -471,6 +471,24 @@ org.opentest4j.AssertionFailedError: expected: <3> but was: <2>
|
||||
expect(r.get("com.acme.UserTest")?.own).toBe("passed");
|
||||
expect(r.summary).toMatchObject({ passed: 2, failed: 1 });
|
||||
});
|
||||
|
||||
it("locates a TestNG failure in the test, past TestNG's own frames", () => {
|
||||
const r = results(
|
||||
jvmParser(),
|
||||
`[INFO] Running com.acme.PriceTest
|
||||
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.2 s <<< FAILURE! -- in com.acme.PriceTest
|
||||
[ERROR] com.acme.PriceTest.rounds -- Time elapsed: 0.01 s <<< FAILURE!
|
||||
java.lang.AssertionError: expected [3] but found [2]
|
||||
at org.testng.Assert.fail(Assert.java:111)
|
||||
at org.testng.Assert.assertEquals(Assert.java:1080)
|
||||
at com.acme.PriceTest.rounds(PriceTest.java:12)
|
||||
`,
|
||||
);
|
||||
expect(leaf(r, "com.acme.PriceTest", "rounds")?.location).toMatchObject({
|
||||
path: "com/acme/PriceTest.java",
|
||||
line: 12,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parser selection", () => {
|
||||
|
||||
Reference in New Issue
Block a user