testng icon indicating copy to clipboard operation
testng copied to clipboard

Failing reports should result in test failure

Open vlsi opened this issue 3 years ago • 11 comments

TestNG Version

The latest

Expected behavior

Failing reports should result in test execution failure. An alternative option is to have a property to "ignore errors from reporters"

Actual behavior

TestNG ignores the error and pretends everything works as usual.

Here's a sample test failure that looks like a passing test case. I guess there's an error in the test, however, TestNG ignores the error, so it hides the bug:

TestNG > Listeners > test.multiplelisteners.TestMaker > run STANDARD_ERROR
    [TestNG] Reporter test.multiplelisteners.SimpleReporter@56865f38 failed
    java.lang.RuntimeException: java.lang.NoSuchFieldException: m_configuration
    	at test.multiplelisteners.SimpleReporter.generateReport(SimpleReporter.java:31)
    	at org.testng.TestNG.generateReports(TestNG.java:1101)
    	at org.testng.TestNG.run(TestNG.java:1044)
    	at test.multiplelisteners.TestMaker.run(TestMaker.java:26)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:136)
    	at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:593)
    	at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:175)
    	at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:49)
    	at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:826)
    	at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:148)
    	at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:147)
    	at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:129)
    	at java.util.ArrayList.forEach(ArrayList.java:1249)
    	at org.testng.TestRunner.privateRun(TestRunner.java:794)
    	at org.testng.TestRunner.run(TestRunner.java:596)
    	at org.testng.SuiteRunner.runTest(SuiteRunner.java:420)
    	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:414)
    	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:375)
    	at org.testng.SuiteRunner.run(SuiteRunner.java:319)
    	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53)
    	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:96)
    	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1220)
    	at org.testng.TestNG.runSuitesLocally(TestNG.java:1142)
    	at org.testng.TestNG.runSuites(TestNG.java:1071)
    	at org.testng.TestNG.run(TestNG.java:1039)
    	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.runTests(TestNGTestClassProcessor.java:141)
    	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.stop(TestNGTestClassProcessor.java:90)
    	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    	at com.sun.proxy.$Proxy2.stop(Unknown Source)
    	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:133)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
    	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
    	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
    	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
    	at java.lang.Thread.run(Thread.java:748)
    Caused by: java.lang.NoSuchFieldException: m_configuration
    	at java.lang.Class.getDeclaredField(Class.java:2070)
    	at test.multiplelisteners.SimpleReporter.generateReport(SimpleReporter.java:24)
    	... 56 more

vlsi avatar May 23 '21 12:05 vlsi

Just in case, the field SuiteRunner#m_configuration was renamed to configuration in 878c315716d6e7a75082a76e7b0f2ce6b7b019b8 in 2016, and I guess it resulted in an unnoticed test failure :(

vlsi avatar May 23 '21 12:05 vlsi

@vlsi - I am not sure I am convinced that errors in reports should trigger test failures.

Lets consider the below scenario.

I have included lets say 5 reporting options in my suite and my tests (lets say there's a large set of tests) run to completion successfully. Now just because a reporter threw an exception, it would be unfair on the user to have their execution marked as failure. This becomes even more true/relevant when the reporting mechanism being consumed by a user is built by someone else.

So IMO, this should not be the case.

@juherr - WDYT ?

krmahadevan avatar May 23 '21 13:05 krmahadevan

This becomes even more true/relevant when the reporting mechanism being consumed by a user is built by someone else.

It can be represented as "configuration method failure" or something like that. In other words, "failing report" might be represented similar to a regular test failure.

The current behavior is "pretend nothing happened" which is confusing.

Apparently, we do not want to ignore unexpected errors, especially when we verify TestNG quality.

vlsi avatar May 23 '21 13:05 vlsi

Now just because a reporter threw an exception, it would be unfair on the user to have their execution marked as failure

That depends on the nature of the failure. If the reporter missed important data, then it might indeed be a true bug detected in the test run.

I agree there might be a case for "ignore reporting errors", however, I would appreciate "surface errors by default" rather than "hide errors by default".


I understand there's backward compatibility, and changing defaults is sad.

So the way out here seems to introduce a property to control the behavior and flip the default in 8.0 or so. Then TestNG test suite could run with strict mode, so the reporting failures could be detected in CI.

vlsi avatar May 23 '21 13:05 vlsi

That depends on the nature of the failure. If the reporter missed important data, then it might indeed be a true bug detected in the test run.

I believe reports are not part of tests and failures in them should be reported to the user, but never break test executions and causing hardships to the users.

however, I would appreciate "surface errors by default" rather than "hide errors by default".

I am fine with adding a configuration parameter, which would be turned OFF by default and which can be turned ON if needed (not vice versa because there's no easy way of conveying this behavior to the user)

krmahadevan avatar May 23 '21 13:05 krmahadevan

Oh, I see your point. Please feel free to close the issue or keep it open then.

If the flag is not going to be the default in the future, I am not interested in implementing it.

vlsi avatar May 23 '21 14:05 vlsi

I believe reports are not part of tests and failures in them should be reported to the user, but never break test executions and causing hardships to the users.

@krmahadevan I disagree: every failure which is not in a test should crash the run because it is not expected. Otherwise, users will have false-positive results and will miss test failure as we did for many years.

For sure, the behavior change should be configurable, and first, the default value could be the current behavior. Didn't check but in every case, the return code must highlight the error (even if I think nobody is checking the value).

juherr avatar May 28 '21 07:05 juherr

@juherr

every failure which is not in a test should crash the run because it is not expected.

There are two ways of looking at it. Consider this scenario. I have 5 types of reporters wired in (a few of them are 3rd party reporters and may have some bug in them that is yet to be fixed). Now my execution is exposing that defect in one of those reporters, but other reporters are working fine.

Are you suggesting that just because one of the reporters had a problem in it, we need to invalidate an execution (which may have run for hours together and user would still have gotten the test results via the other reporters that were part of the same execution) ?

To me that is bad experience. Reports are just ways to report the execution results. Just because that reporting failed doesnt mean that my execution itself failed.

When a reporter fails, here's how I read it as : TestNG executed all the tests and they all passed, but I failed to report the results in a proper way because of some problem

the return code must highlight the error (even if I think nobody is checking the value).

I think surefire plugin checks this no, and based on that only they report pass/fail no ?

krmahadevan avatar May 28 '21 07:05 krmahadevan

To me that is bad experience. Reports are just ways to report the execution results. Just because that reporting failed doesnt mean that my execution itself failed.

I understand your point of view but users have many workarounds like removing the failing reporter from the execution.

Are you suggesting that just because one of the reporters had a problem in it, we need to invalidate an execution (which may have run for hours together and user would still have gotten the test results via the other reporters that were part of the same execution) ?

Right, maybe we can store all reporters exceptions and throw them at the end of the run. Could be a better experience. But I prefer a crash instead of a false-positive (and I suppose QA managers too :p)

I think surefire plugin checks this no, and based on that only they report pass/fail no ?

No idea. They should :)

juherr avatar May 28 '21 07:05 juherr

I understand your point of view but users have many workarounds like removing the failing reporter from the execution.

But that would be only after an execution would have failed right ?

Right, maybe we can store all reporters exceptions and throw them at the end of the run. Could be a better experience.

What if we just threw out warnings at the end, if we encountered problems in reports ? That way, the user would get to know that there were problems in 1 or more reporters, and the execution would also have run to completion.

and I suppose QA managers too

Not when it involves a nightly run that ran for a few hours only to learn in the morning that the execution failed because a reporter conked.

krmahadevan avatar May 28 '21 08:05 krmahadevan

Not when it involves a nightly run that ran for a few hours only to learn in the morning that the execution failed because a reporter conked

Shit happens :D

What if we just threw out warnings at the end, if we encountered problems in reports ? That way, the user would get to know that there were problems in 1 or more reporters, and the execution would also have run to completion.

We can start with error messages (it is more than warnings because something really failed in the execution). The problem with error messages is that nobody reads them, so they will never open issues and bugs won't be fixed. That's why I'd prefer a real failure of the run next, which can be fixed by a configuration. Then, if users prefer to change the default behavior, it's their responsibility.

juherr avatar May 28 '21 08:05 juherr