Clean up console output of unit test runs (#11134)

* Allow tests to discard console output selectively, by default collect and discard unless result is failure

* Fix debug change and a little source clarification
This commit is contained in:
SomeTroglodyte
2024-02-16 10:57:42 +01:00
committed by GitHub
parent 63243fd775
commit df7072b550
4 changed files with 143 additions and 106 deletions

View File

@ -17,7 +17,6 @@ import com.unciv.models.translations.squareBraceRegex
import com.unciv.models.translations.tr
import com.unciv.testing.GdxTestRunner
import com.unciv.utils.Log
import com.unciv.utils.debug
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@ -84,7 +83,8 @@ class TranslationTests {
@Test
fun translationsFromJSONsCanBeGenerated() {
// it triggers generation of the translation's strings
// Triggers generation of the translation's strings
// Will output "Translation writer took...", which is suppressed unless you use the @RedirectOutput(RedirectPolicy.Show) annotation
val stringsSize = TranslationFileWriter.getGeneratedStringsSize()
Assert.assertTrue("This test will only pass when all .json files are serializable",
@ -308,7 +308,6 @@ class TranslationTests {
addTranslation("best friend", "closest ally")
addTranslation("America", "The old British colonies")
debug("[Dad] and [my [best friend]]".getPlaceholderText())
Assert.assertEquals(listOf("Dad","my [best friend]"),
"[Dad] and [my [best friend]]".getPlaceholderParameters())
Assert.assertEquals("Father and indeed mine own closest ally", "[Dad] and [my [best friend]]".tr())

View File

@ -1,103 +0,0 @@
/*******************************************************************************
* Copyright 2015 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.unciv.testing;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.headless.HeadlessApplication;
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
import com.badlogic.gdx.graphics.GL20;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import static org.mockito.Mockito.*;
public class GdxTestRunner extends BlockJUnit4ClassRunner implements ApplicationListener {
private final Map<FrameworkMethod, RunNotifier> invokeInRender = new HashMap<>();
public GdxTestRunner(Class<?> klass) throws InitializationError {
super(klass);
HeadlessApplicationConfiguration conf = new HeadlessApplicationConfiguration();
new HeadlessApplication(this, conf);
Gdx.gl = mock(GL20.class);
}
@Override
public void create() {
}
@Override
public void resume() {
}
@Override
public void render() {
synchronized (invokeInRender) {
for (Map.Entry<FrameworkMethod, RunNotifier> each : invokeInRender.entrySet()) {
super.runChild(each.getKey(), each.getValue());
}
invokeInRender.clear();
}
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void dispose() {
}
@Override
protected void runChild(FrameworkMethod method, RunNotifier notifier) {
synchronized (invokeInRender) {
// add for invoking in render phase, where gl context is available
invokeInRender.put(method, notifier);
}
// wait until that test was invoked
waitUntilInvokedInRenderMethod();
}
/**
*
*/
private void waitUntilInvokedInRenderMethod() {
try {
while (true) {
Thread.sleep(10);
synchronized (invokeInRender) {
if (invokeInRender.isEmpty())
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,126 @@
/*******************************************************************************
* Copyright 2015 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.unciv.testing
import com.badlogic.gdx.ApplicationListener
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.backends.headless.HeadlessApplication
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration
import com.badlogic.gdx.graphics.GL20
import org.junit.runner.notification.Failure
import org.junit.runner.notification.RunListener
import org.junit.runner.notification.RunNotifier
import org.junit.runners.BlockJUnit4ClassRunner
import org.junit.runners.model.FrameworkMethod
import org.mockito.Mockito
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.io.PrintStream
class GdxTestRunner(klass: Class<*>?) : BlockJUnit4ClassRunner(klass), ApplicationListener {
private val invokeInRender: MutableMap<FrameworkMethod, RunNotifier> = HashMap()
init {
val conf = HeadlessApplicationConfiguration()
HeadlessApplication(this, conf)
Gdx.gl = Mockito.mock(GL20::class.java)
}
// ApplicationListener interface
override fun create() {}
override fun resume() {}
override fun resize(width: Int, height: Int) {}
override fun pause() {}
override fun dispose() {}
override fun render() {
synchronized(invokeInRender) {
for ((method, notifier) in invokeInRender) {
val redirect = method.getAnnotation(RedirectOutput::class.java)
?.policy
?: RedirectPolicy.ShowOnFailure
when (redirect) {
RedirectPolicy.ShowOnFailure ->
runChildRedirectingOutput(method, notifier)
RedirectPolicy.Discard ->
runChildDiscardingOutput(method, notifier)
else ->
super.runChild(method, notifier)
}
}
invokeInRender.clear()
}
}
// BlockJUnit4ClassRunner interface
override fun runChild(method: FrameworkMethod, notifier: RunNotifier) {
synchronized(invokeInRender) {
// add for invoking in render phase, where gl context is available
invokeInRender.put(method, notifier)
}
// wait until that test was invoked
waitUntilInvokedInRenderMethod()
}
private fun waitUntilInvokedInRenderMethod() {
try {
while (true) {
Thread.sleep(10)
if (synchronized(invokeInRender) { invokeInRender.isEmpty() })
break
}
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
// Standard output redirection
private fun runChildRedirectingOutput(method: FrameworkMethod, notifier: RunNotifier) {
val outputBuffer = ByteArrayOutputStream(2048)
val outputStream = PrintStream(outputBuffer)
val oldOutputStream = System.out
val listener = object : RunListener() {
override fun testFailure(failure: Failure?) {
outputBuffer.writeTo(oldOutputStream)
super.testFailure(failure)
}
}
System.setOut(outputStream)
notifier.addListener(listener)
try {
super.runChild(method, notifier)
} finally {
outputStream.close()
System.setOut(oldOutputStream)
notifier.removeListener(listener)
}
}
private fun runChildDiscardingOutput(method: FrameworkMethod, notifier: RunNotifier) {
val oldOutputStream = System.out
System.setOut(PrintStream(object : OutputStream() {
override fun write(codepoint: Int) {}
}))
try {
super.runChild(method, notifier)
} finally {
System.setOut(oldOutputStream)
}
}
}

View File

@ -0,0 +1,15 @@
package com.unciv.testing
enum class RedirectPolicy { Show, ShowOnFailure, Discard }
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
/**
* This annotation controls the [GdxTestRunner] feature to redirect and discard console output from tests.
*
* Settings:
* * [RedirectPolicy.Discard]: Output is discarded.
* * [RedirectPolicy.ShowOnFailure] (**default**): Collected output is written to the console only when the test fails).
* * [RedirectPolicy.Show]: Do not redirect, show console output immediately.
*/
annotation class RedirectOutput(val policy: RedirectPolicy)