8 филиалов для try с ресурсами-возможно покрытие jacoco?
у меня есть код, который использует try с ресурсами, и в jacoco он подходит только наполовину. Все строки исходного кода зеленые, но я получаю маленький желтый символ, говорящий мне, что только 4 из 8 ветвей покрыты.
у меня возникли проблемы с выяснением того, что все ветви, и как написать код, который охватывает их. Три возможных места броска PipelineException
. Это createStageList()
,processItem()
и подразумеваемые close()
- не бросая любые исключения,
- исключение из
createStageList()
- исключение из
processItem()
- исключение из
close()
- исключение из
processItem()
иclose()
Я не могу думать о каких-либо других случаях, но у меня все еще есть только 4 из 8 охваченных.
может кто-нибудь объяснить мне, почему это 4 из 8 и есть ли в любом случае, чтобы поразить все 8 ветвей? Я не умею с ними обращаться расшифровка/чтение / интерпретация байтового кода, но, возможно, вы... :) Я уже видел https://github.com/jacoco/jacoco/issues/82, но ни он, ни проблема, на которую он ссылается, не очень помогают (кроме того, что это связано с генерируемыми компилятором блоками)
Хм, как только я закончу писать это, у меня возникла мысль о том, что случай(ы) не может быть не проверен тем, что я упоминал выше... Я отправлю ответ, если я правильно понял. Я уверен, что этот вопрос и ответ поможет кто-то в любом случае.
EDIT: Нет, я его не нашел. Метание RuntimeExceptions (не обрабатывается блоком catch) больше не охватывало ветви
6 ответов:
Ну я не могу сказать вам, что именно проблема с Jacoco, но я могу показать вам, как Try с ресурсами компилируется. В принципе, существует множество генерируемых компилятором коммутаторов для обработки исключений, возникающих в различных точках.
если мы возьмем следующий код и скомпилируем его
public static void main(String[] args){ String a = "before"; try (CharArrayWriter br = new CharArrayWriter()) { br.writeTo(null); } catch (IOException e){ System.out.println(e.getMessage()); } String a2 = "after"; }
а потом разбираем, получаем
.method static public main : ([Ljava/lang/String;)V .limit stack 2 .limit locals 7 .catch java/lang/Throwable from L26 to L30 using L33 .catch java/lang/Throwable from L13 to L18 using L51 .catch [0] from L13 to L18 using L59 .catch java/lang/Throwable from L69 to L73 using L76 .catch [0] from L51 to L61 using L59 .catch java/io/IOException from L3 to L94 using L97 ldc 'before' astore_1 L3: new java/io/CharArrayWriter dup invokespecial java/io/CharArrayWriter <init> ()V astore_2 aconst_null astore_3 L13: aload_2 aconst_null invokevirtual java/io/CharArrayWriter writeTo (Ljava/io/Writer;)V L18: aload_2 ifnull L94 aload_3 ifnull L44 L26: aload_2 invokevirtual java/io/CharArrayWriter close ()V L30: goto L94 L33: .stack full locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable stack Object java/lang/Throwable .end stack astore 4 aload_3 aload 4 invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V goto L94 L44: .stack same aload_2 invokevirtual java/io/CharArrayWriter close ()V goto L94 L51: .stack same_locals_1_stack_item stack Object java/lang/Throwable .end stack astore 4 aload 4 astore_3 aload 4 athrow L59: .stack same_locals_1_stack_item stack Object java/lang/Throwable .end stack astore 5 L61: aload_2 ifnull L91 aload_3 ifnull L87 L69: aload_2 invokevirtual java/io/CharArrayWriter close ()V L73: goto L91 L76: .stack full locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable Top Object java/lang/Throwable stack Object java/lang/Throwable .end stack astore 6 aload_3 aload 6 invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V goto L91 L87: .stack same aload_2 invokevirtual java/io/CharArrayWriter close ()V L91: .stack same aload 5 athrow L94: .stack full locals Object [Ljava/lang/String; Object java/lang/String stack .end stack goto L108 L97: .stack same_locals_1_stack_item stack Object java/io/IOException .end stack astore_2 getstatic java/lang/System out Ljava/io/PrintStream; aload_2 invokevirtual java/io/IOException getMessage ()Ljava/lang/String; invokevirtual java/io/PrintStream println (Ljava/lang/String;)V L108: .stack same ldc 'after' astore_2 return .end method
для тех, кто не говорит байт-код, это примерно эквивалентно следующей псевдо Java. Мне пришлось использовать gotos, потому что байт-код на самом деле не соответствует потоку управления Java.
как вы можете видеть, есть много случаев для обработки различных возможностей подавленных исключений. Это не разумно, чтобы иметь возможность охватить все эти случаи. На самом деле,
goto L59
ветвь с первой попытки блока невозможно достичь, так как первый catch Throwable будет ловить все исключения.try{ CharArrayWriter br = new CharArrayWriter(); Throwable x = null; try{ br.writeTo(null); } catch (Throwable t) {goto L51;} catch (Throwable t) {goto L59;} if (br != null) { if (x != null) { try{ br.close(); } catch (Throwable t) { x.addSuppressed(t); } } else {br.close();} } break; try{ L51: x = t; throw t; L59: Throwable t2 = t; } catch (Throwable t) {goto L59;} if (br != null) { if (x != null) { try{ br.close(); } catch (Throwable t){ x.addSuppressed(t); } } else {br.close();} } throw t2; } catch (IOException e) { System.out.println(e) }
Я могу охватить все 8 ветвей, поэтому мой ответ-да. Посмотрите на следующий код, это только быстрая попытка, но она работает (или см. Мой github:https://github.com/bachoreczm/basicjava и пакет' trywithresources', там вы можете найти, как работает try-with-resources, см. Класс' ExplanationOfTryWithResources'):
import java.io.ByteArrayInputStream; import java.io.IOException; import org.junit.Test; public class TestAutoClosable { private boolean isIsNull = false; private boolean logicThrowsEx = false; private boolean closeThrowsEx = false; private boolean getIsThrowsEx = false; private void autoClose() throws Throwable { try (AutoCloseable is = getIs()) { doSomething(); } catch (Throwable t) { System.err.println(t); } } @Test public void test() throws Throwable { try { getIsThrowsEx = true; autoClose(); } catch (Throwable ex) { getIsThrowsEx = false; } } @Test public void everythingOk() throws Throwable { autoClose(); } @Test public void logicThrowsException() { try { logicThrowsEx = true; everythingOk(); } catch (Throwable ex) { logicThrowsEx = false; } } @Test public void isIsNull() throws Throwable { isIsNull = true; everythingOk(); isIsNull = false; } @Test public void closeThrow() { try { closeThrowsEx = true; logicThrowsEx = true; everythingOk(); closeThrowsEx = false; } catch (Throwable ex) { } } @Test public void test2() throws Throwable { try { isIsNull = true; logicThrowsEx = true; everythingOk(); } catch (Throwable ex) { isIsNull = false; logicThrowsEx = false; } } private void doSomething() throws IOException { if (logicThrowsEx) { throw new IOException(); } } private AutoCloseable getIs() throws IOException { if (getIsThrowsEx) { throw new IOException(); } if (closeThrowsEx) { return new ByteArrayInputStream("".getBytes()) { @Override public void close() throws IOException { throw new IOException(); } }; } if (!isIsNull) { return new ByteArrayInputStream("".getBytes()); } return null; } }
нет реального вопроса, но хотел бросить больше исследований там. tl;dr = похоже, что вы можете достичь 100% покрытия для try-finally, но не для try-with-resource.
понятно, что есть разница между старой школой try-finally и Java7 try-with-resources. Вот два эквивалентных примера, показывающие то же самое, используя альтернативные подходы.
пример старой школы (попытка-наконец подход):
final Statement stmt = conn.createStatement(); try { foo(); if (stmt != null) { stmt.execute("SELECT 1"); } } finally { if (stmt != null) stmt.close(); }
Java7 пример (try-with-resource подход):
try (final Statement stmt = conn.createStatement()) { foo(); if (stmt != null) { stmt.execute("SELECT 1"); } }
анализ: олдскульный пример:
Используя Jacoco 0.7.4.201502262128 и JDK 1.8.0_45, я смог получить 100% покрытие линии, инструкции и филиала на примере старой школы, используя следующие 4 теста:Jacoco указывает на 2 ветви внутри " try " (на нулевой проверке) и 4 внутри finally (на нулевой проверке). Все они покрыты полностью.
- Basic grease path (оператор not null, а execute () выполняется нормально)
- execute () бросает исключение
- foo () выбрасывает исключение и оператор возвращается как null
- оператор возвращается как null
анализ: java-7 пример:
Если те же самые 4 теста выполняются против примера стиля Java7, jacoco указывает, что 6/8 ветвей покрыты (на самой попытке) и 2/2 на нулевой проверке в пределах попытки. Я попробовал ряд дополнительных тестов, чтобы увеличить охват, но я не могу найти способ получить лучше, чем 6/8. Как указывали другие, декомпилированный код (который я также рассматривал) для примера java-7 предполагает, что компилятор java генерирует недостижимые сегменты для try-with-resource. Jacoco сообщает (точно), что такие отрезки существуют.обновление: используя стиль кодирования Java7, вы можете получить 100% покрытие если С помощью Java7 JRE (см. ответ Matyas ниже). Однако, используя стиль кодирования Java7 с Java8 JRE, я верю вам ударит 6/8 ветвей покрыты. Тот же код, только другой JRE. Похоже, что байтовый код создается по-разному между двумя JRE с Java8, создающим недостижимые пути.
четыре года, но все же...
- счастливый путь с ненулевым
AutoCloseable
- счастливый путь с null
AutoCloseable
- бросает на запись
- бросает на близких
- выбрасывает на запись и закрыть!--19-->
- бросает в спецификации ресурса (С часть, например, вызов конструктора)
- бросает в
try
блок аAutoCloseable
равно nullВыше перечислены все 7 условий - причина для 8 ветвей происходит из-за повторного состояния.
все ветви могут быть достигнуты,
try-with-resources
довольно простой компилятор сахара (по крайней мере, по сравнению сswitch-on-string
) - если они не могут быть достигнуты, то это по определению ошибка компилятора.на самом деле требуется только 6 модульных тестов (в приведенном ниже примере кода,
throwsOnClose
и@Ingore
D и охват ветви 8/8.также обратите внимание, что Throwable.addSuppressed (Throwable) не может подавить сам по себе, поэтому сгенерированный байт - код содержит дополнительную защиту (if_acmpeq-ссылочное равенство), чтобы предотвратить это). К счастью, эта ветвь покрыта случаями throw-on-write, throw-on-close и throw-on-write-close, поскольку слоты переменных байт-кода повторно используются внешними 2 из 3 областей обработчика исключений.
это не проблема с Jacoco-на самом деле пример кода в связанном выпуск #82 неверно, так как нет дублированного null проверяет и нет вложенного блока catch, окружающих вблизи.
JUnit тест, демонстрирующий 8 из 8 ветвей покрыты
import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; import org.junit.Ignore; import org.junit.Test; public class FullBranchCoverageOnTryWithResourcesTest { private static class DummyOutputStream extends OutputStream { private final IOException thrownOnWrite; private final IOException thrownOnClose; public DummyOutputStream(IOException thrownOnWrite, IOException thrownOnClose) { this.thrownOnWrite = thrownOnWrite; this.thrownOnClose = thrownOnClose; } @Override public void write(int b) throws IOException { if(thrownOnWrite != null) { throw thrownOnWrite; } } @Override public void close() throws IOException { if(thrownOnClose != null) { throw thrownOnClose; } } } private static class Subject { private OutputStream closeable; private IOException exception; public Subject(OutputStream closeable) { this.closeable = closeable; } public Subject(IOException exception) { this.exception = exception; } public void scrutinize(String text) { try(OutputStream closeable = create()) { process(closeable); } catch(IOException e) { throw new UncheckedIOException(e); } } protected void process(OutputStream closeable) throws IOException { if(closeable != null) { closeable.write(1); } } protected OutputStream create() throws IOException { if(exception != null) { throw exception; } return closeable; } } private final IOException onWrite = new IOException("Two writes don't make a left"); private final IOException onClose = new IOException("Sorry Dave, we're open 24/7"); /** * Covers one branch */ @Test public void happyPath() { Subject subject = new Subject(new DummyOutputStream(null, null)); subject.scrutinize("text"); } /** * Covers one branch */ @Test public void happyPathWithNullCloseable() { Subject subject = new Subject((OutputStream) null); subject.scrutinize("text"); } /** * Covers one branch */ @Test public void throwsOnCreateResource() { IOException chuck = new IOException("oom?"); Subject subject = new Subject(chuck); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(chuck))); } } /** * Covers three branches */ @Test public void throwsOnWrite() { Subject subject = new Subject(new DummyOutputStream(onWrite, null)); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(onWrite))); } } /** * Covers one branch - Not needed for coverage if you have the other tests */ @Ignore @Test public void throwsOnClose() { Subject subject = new Subject(new DummyOutputStream(null, onClose)); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(onClose))); } } /** * Covers two branches */ @SuppressWarnings("unchecked") @Test public void throwsOnWriteAndClose() { Subject subject = new Subject(new DummyOutputStream(onWrite, onClose)); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(onWrite))); assertThat(e.getCause().getSuppressed(), is(arrayContaining(sameInstance(onClose)))); } } /** * Covers three branches */ @Test public void throwsInTryBlockButCloseableIsNull() throws Exception { IOException chucked = new IOException("ta-da"); Subject subject = new Subject((OutputStream) null) { @Override protected void process(OutputStream closeable) throws IOException { throw chucked; } }; try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(chucked))); } } }
будьте осторожны
хотя и не в примере кода OP, есть один случай, который не может быть протестирован AFAIK.
если вы передаете ссылку на ресурс в качестве аргумента, то в Java 7/8 вы должны иметь локальную переменную:
void someMethod(AutoCloseable arg) { try(AutoCloseable pfft = arg) { //... } }
в этом случае сгенерированный код по-прежнему будет охранять ссылку на ресурс. Синтаксический сахар-это Обновлено в Java 9, где локальная переменная больше не требуется:
try(arg){ /*...*/ }
дополнение-предложите использовать библиотеку, чтобы полностью избежать ветвей
по общему признанию, некоторые из этих ветвей могут быть списаны как нереалистичные-т. е. где блок try использует
AutoCloseable
без нулевой проверки или где ссылка на ресурс (with
) не может быть ноль.часто ваше приложение не заботится о том, где оно не удалось - открыть файл, записать в него или закрыть его - степень детализации сбоя не имеет значения (если приложение специально не связано с файлами, например, файловым браузером или текстовым процессором).
кроме того, в коде OP, чтобы проверить нулевой закрываемый путь - вам нужно будет рефакторировать блок try в защищенный метод, подкласс и обеспечить реализацию NOOP - все это просто получить покрытие ветви, которые никогда не будут взяты в дикой природе.
я написал крошечную библиотеку Java 8 io.earcam.заурядный (in Maven Central), который имеет дело с наиболее проверенным шаблоном исключения.
относится к этому вопросу: он предоставляет кучу нулевой ветви, однострочные для
AutoCloseable
s, преобразование проверенных исключений в непроверенные.Пример: Free Port Finder
int port = Closing.closeAfterApplying(ServerSocket::new, 0, ServerSocket::getLocalPort);
Jacoco недавно исправил эту проблему, выпуск 0.8.0 (2018/01/02)
" при создании отчетов отфильтровываются различные сгенерированные компилятором артефакты, которые в противном случае требуют ненужных и иногда невозможных трюков, чтобы не иметь частичного или пропущенного покрытия:
- часть байт-кода для операторов try-with-resources (GitHub #500)."