Commit 13fc7563 for xz

commit 13fc756371c5e84f11a3ad9d0472dff8dc0cb2ed
Author: Lasse Collin <lasse.collin@tukaani.org>
Date:   Wed Nov 4 19:30:21 2026 +0200

    Tests: Add decode_byte_by_byte and use it from test_files.sh

    It exercises some corner cases. Without the previous commit, this fails
    on good-known_size-with_eopm.lzma.

diff --git a/tests/Makefile.am b/tests/Makefile.am
index b29967e7..5327cd00 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -37,6 +37,7 @@ endif

 check_PROGRAMS = \
 	create_compress_files \
+	decode_byte_by_byte \
 	test_check \
 	test_hardware \
 	test_stream_buffer_decode \
diff --git a/tests/decode_byte_by_byte.c b/tests/decode_byte_by_byte.c
new file mode 100644
index 00000000..e2a1b369
--- /dev/null
+++ b/tests/decode_byte_by_byte.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: 0BSD
+
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file       decode_byte_by_byte.c
+/// \brief      Decodes a file one byte at a time
+//
+//  Author:     Lasse Collin
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "sysdefs.h"
+#include "lzma.h"
+#include <stdio.h>
+
+
+#define MEMLIMIT (UINT64_C(5) << 20)
+
+#define FAIL(msg) \
+	do { \
+		fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], msg); \
+		return EXIT_FAILURE; \
+	} while (0)
+
+
+int
+main(int argc, char **argv)
+{
+	if (argc != 2)
+		FAIL("One filename is required");
+
+	FILE *f = fopen(argv[1], "rb");
+	if (f == NULL)
+		FAIL("Opening the file failed");
+
+	lzma_stream strm = LZMA_STREAM_INIT;
+	if (lzma_auto_decoder(&strm, MEMLIMIT, LZMA_CONCATENATED) != LZMA_OK)
+		FAIL("Decoder initialization failed");
+
+	bool eof = false;
+
+	while (true) {
+		uint8_t in[1];
+		uint8_t out[1];
+
+		if (!eof) {
+			const int b = fgetc(f);
+			if (b == EOF) {
+				if (ferror(f))
+					FAIL("Error reading the input file");
+
+				eof = true;
+			} else {
+				in[0] = (uint8_t)b;
+				strm.next_in = in;
+				strm.avail_in = 1;
+			}
+		}
+
+		do {
+			strm.next_out = out;
+			strm.avail_out = 1;
+
+			const lzma_action action
+					= eof ? LZMA_FINISH : LZMA_RUN;
+			const lzma_ret ret = lzma_code(&strm, action);
+
+			if (strm.avail_out == 0) {
+				if (fputc(out[0], stdout) == EOF)
+					FAIL("Error writing to stdout");
+			}
+
+			if (ret == LZMA_STREAM_END)
+				goto end;
+
+			if (ret != LZMA_OK) {
+				fprintf(stderr, "%s: %s: "
+						"Decoder error (ret = %d)\n",
+						argv[0], argv[1], ret);
+				return EXIT_FAILURE;
+			}
+		} while (strm.avail_out == 0);
+	}
+
+end:
+	// We must have consumed the entire file.
+	if (fgetc(f) != EOF)
+		FAIL("Garbage at the end of the file");
+
+	if (ferror(f))
+		FAIL("Error reading the input file (end)");
+
+	lzma_end(&strm);
+	(void)fclose(f);
+	return EXIT_SUCCESS;
+}
diff --git a/tests/test_files.sh b/tests/test_files.sh
index 9312de94..1cfe27be 100755
--- a/tests/test_files.sh
+++ b/tests/test_files.sh
@@ -105,6 +105,8 @@ do
 		echo "Good file failed: $I"
 		exit 1
 	fi
+
+	./decode_byte_by_byte "$I" > /dev/null || exit 1
 done

 for I in "$srcdir"/files/bad-*.xz
@@ -185,6 +187,8 @@ do
 		echo "Good file failed: $I"
 		exit 1
 	fi
+
+	./decode_byte_by_byte "$I" > /dev/null || exit 1
 done

 for I in "$srcdir"/files/bad-*.lzma
@@ -209,6 +213,11 @@ if have_feature LZIP_DECODER ".lz files" ; then
 			echo "Good file failed: $I"
 			exit 1
 		fi
+
+		case $I in
+			*trailing*) ;;
+			*) ./decode_byte_by_byte "$I" > /dev/null || exit 1 ;;
+		esac
 	done

 	for I in "$srcdir"/files/bad-*.lz "$srcdir"/files/unsupported-*.lz
diff --git a/tests/tests.cmake b/tests/tests.cmake
index 5035f7b0..f2abc089 100644
--- a/tests/tests.cmake
+++ b/tests/tests.cmake
@@ -216,6 +216,18 @@ if(BUILD_TESTING)
         # files outside the build directory!
         file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test_files")

+        # test_files.sh needs the decode_byte_by_byte program.
+        # Put the executable in the above-created directory so that
+        # test_files.sh can run it with ./decode_byte_by_byte.
+        add_executable(decode_byte_by_byte tests/decode_byte_by_byte.c)
+        target_include_directories(decode_byte_by_byte PRIVATE
+            src/common
+            src/liblzma/api
+        )
+        target_link_libraries(decode_byte_by_byte PRIVATE liblzma tests_w32res)
+        set_target_properties(decode_byte_by_byte PROPERTIES
+                              RUNTIME_OUTPUT_DIRECTORY test_files)
+
         add_test(NAME test_files.sh
             COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_files.sh" ".."
             WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test_files"