From 2dbe7b38a7319a7323946574bae96362464c2efd Mon Sep 17 00:00:00 2001 From: Nik Srnka Date: Tue, 10 Mar 2026 20:46:56 -0400 Subject: [PATCH 1/2] Updated readInt so streams are closed --- app/src/processing/app/UpdateCheck.java | 8 +++++--- app/test/processing/app/UpdateCheckTest.java | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 app/test/processing/app/UpdateCheckTest.java diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index 20c91dd38..351ae2724 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -206,10 +206,12 @@ protected boolean promptToOpenContributionManager() { protected int readInt(String filename) throws IOException { URL url = new URL(filename); - InputStream stream = url.openStream(); + // try-with-resources auto closes things of type "Closeable" even the code throws an error + try(InputStream stream = url.openStream(); InputStreamReader isr = new InputStreamReader(stream); - BufferedReader reader = new BufferedReader(isr); - return Integer.parseInt(reader.readLine()); + BufferedReader reader = new BufferedReader(isr)) { + return Integer.parseInt(reader.readLine()); + } } diff --git a/app/test/processing/app/UpdateCheckTest.java b/app/test/processing/app/UpdateCheckTest.java new file mode 100644 index 000000000..7a0d618fe --- /dev/null +++ b/app/test/processing/app/UpdateCheckTest.java @@ -0,0 +1,4 @@ +package processing.app; + +public class UpdateCheckTest { +} From b9b794e3c4e88fb30aaf5a646ee14636f66a3c7f Mon Sep 17 00:00:00 2001 From: Nik Srnka Date: Wed, 18 Mar 2026 14:07:30 -0400 Subject: [PATCH 2/2] made tests for UpdateCheckTest.java and updated necessary files --- app/build.gradle.kts | 3 + app/src/processing/app/UpdateCheck.java | 5 +- app/test/processing/app/UpdateCheckTest.java | 151 ++++++++++++++++++- 3 files changed, 156 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4f91e6d98..da050805c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -44,6 +44,9 @@ sourceSets{ kotlin{ srcDirs("test") } + java{ + srcDirs("test") + } } } diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index 351ae2724..debe32417 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -204,13 +204,14 @@ protected boolean promptToOpenContributionManager() { */ - protected int readInt(String filename) throws IOException { + protected static int readInt(String filename) throws IOException { URL url = new URL(filename); + // try-with-resources auto closes things of type "Closeable" even the code throws an error try(InputStream stream = url.openStream(); InputStreamReader isr = new InputStreamReader(stream); BufferedReader reader = new BufferedReader(isr)) { - return Integer.parseInt(reader.readLine()); + return Integer.parseInt(reader.readLine().trim()); } } diff --git a/app/test/processing/app/UpdateCheckTest.java b/app/test/processing/app/UpdateCheckTest.java index 7a0d618fe..9dbc214b9 100644 --- a/app/test/processing/app/UpdateCheckTest.java +++ b/app/test/processing/app/UpdateCheckTest.java @@ -1,4 +1,153 @@ package processing.app; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; -public class UpdateCheckTest { +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class UpdateCheckTest { + + @TempDir + Path tempDir; + + // Helper: write content to a temp file and return its URL string + private String createTempFile(String content) throws IOException { + Path file = tempDir.resolve("test.txt"); + Files.writeString(file, content, StandardCharsets.UTF_8); + return file.toUri().toString(); + } + + + // tests to show that the method returns what it should + @Test + void readInt_simpleInteger_returnsCorrectValue() throws IOException { + String url = createTempFile("42\n"); + assertEquals(42, UpdateCheck.readInt(url)); + } + + @Test + void readInt_negativeInteger_returnsCorrectValue() throws IOException { + String url = createTempFile("-7\n"); + assertEquals(-7, UpdateCheck.readInt(url)); + } + + @Test + void readInt_integerWithLeadingAndTrailingWhitespace_returnsCorrectValue() throws IOException { + String url = createTempFile(" 100 \n"); + assertEquals(100, UpdateCheck.readInt(url)); + } + + @Test + void readInt_integerWithNoNewline_returnsCorrectValue() throws IOException { + String url = createTempFile("99"); + assertEquals(99, UpdateCheck.readInt(url)); + } + + @Test + void readInt_maxInt_returnsCorrectValue() throws IOException { + String url = createTempFile(String.valueOf(Integer.MAX_VALUE)); + assertEquals(Integer.MAX_VALUE, UpdateCheck.readInt(url)); + } + + @Test + void readInt_minInt_returnsCorrectValue() throws IOException { + String url = createTempFile(String.valueOf(Integer.MIN_VALUE)); + assertEquals(Integer.MIN_VALUE, UpdateCheck.readInt(url)); + } + + @Test + void readInt_integerWithMultipleLines_readsOnlyFirstLine() throws IOException { + String url = createTempFile("5\n10\n15"); + assertEquals(5, UpdateCheck.readInt(url)); + } + + // checks for if errors are correctly reported + @Test + void readInt_nonNumericContent_throwsNumberFormatException() throws IOException { + String url = createTempFile("not-a-number\n"); + assertThrows(NumberFormatException.class, () -> UpdateCheck.readInt(url)); + } + + @Test + void readInt_emptyFile_throwsNullPointerException() throws IOException { + String url = createTempFile(""); + // readLine() returns null on empty stream → trim() throws NPE + assertThrows(Exception.class, () -> UpdateCheck.readInt(url)); + } + + @Test + void readInt_blankLine_throwsNumberFormatException() throws IOException { + String url = createTempFile(" \n"); + assertThrows(NumberFormatException.class, () -> UpdateCheck.readInt(url)); + } + + @Test + void readInt_floatValue_throwsNumberFormatException() throws IOException { + String url = createTempFile("3.14\n"); + assertThrows(NumberFormatException.class, () -> UpdateCheck.readInt(url)); + } + + @Test + void readInt_overflowValue_throwsNumberFormatException() throws IOException { + String url = createTempFile("99999999999999\n"); + assertThrows(NumberFormatException.class, () -> UpdateCheck.readInt(url)); + } + + @Test + void readInt_invalidUrl_throwsMalformedURLException() { + assertThrows(MalformedURLException.class, + () -> UpdateCheck.readInt("not-a-valid-url")); + } + + @Test + void readInt_nonExistentFile_throwsIOException() { + String nonExistent = tempDir.resolve("ghost.txt").toUri().toString(); + assertThrows(IOException.class, () -> UpdateCheck.readInt(nonExistent)); + } + + // checks for if streams are closed + @Test + void readInt_streamIsClosedAfterSuccessfulRead() throws IOException { + // Spy on the InputStream to verify close() is called + Path file = tempDir.resolve("close_test.txt"); + Files.writeString(file, "7", StandardCharsets.UTF_8); + + URL url = file.toUri().toURL(); + InputStream realStream = url.openStream(); + InputStream spyStream = spy(realStream); + + try (MockedConstruction mockedUrl = mockConstruction(URL.class, + (mock, ctx) -> when(mock.openStream()).thenReturn(spyStream))) { + + UpdateCheck.readInt(file.toUri().toString()); + } + + verify(spyStream, atLeastOnce()).close(); + } + + @Test + void readInt_streamIsClosedEvenWhenParseThrows() throws IOException { + Path file = tempDir.resolve("bad_close_test.txt"); + Files.writeString(file, "not-a-number", StandardCharsets.UTF_8); + + URL url = file.toUri().toURL(); + InputStream realStream = url.openStream(); + InputStream spyStream = spy(realStream); + + try (MockedConstruction mockedUrl = mockConstruction(URL.class, + (mock, ctx) -> when(mock.openStream()).thenReturn(spyStream))) { + + assertThrows(NumberFormatException.class, + () -> UpdateCheck.readInt(file.toUri().toString())); + } + + verify(spyStream, atLeastOnce()).close(); + } }