1# test-compress.cmake -- Runs a test against an input file to make sure that the specified
2#   targets are able to to compress and then decompress it successfully. Optionally verify
3#   the results with gzip. Output files are generated with unique names to prevent parallel
4#   tests from corrupting one another. Default target arguments are compatible with minigzip.
5
6# Copyright (C) 2021 Nathan Moinvaziri
7# Licensed under the Zlib license, see LICENSE.md for details
8
9# that test a specific input file for compression or decompression.
10
11# Required Variables
12#   INPUT                   - Input file to test
13#   TARGET or               - Command to run for both compress and decompress
14#     COMPRESS_TARGET and   - Command to run to compress input file
15#     DECOMPRESS_TARGET     - Command to run to decompress output file
16
17# Optional Variables
18#   TEST_NAME               - Name of test to use when constructing output file paths
19#   COMPRESS_ARGS           - Arguments to pass for compress command (default: -c -k)
20#   DECOMPRESS_ARGS         - Arguments to pass to decompress command (default: -d -c)
21
22#   GZIP_VERIFY             - Verify that gzip can decompress the COMPRESS_TARGET output and
23#                             verify that DECOMPRESS_TARGET can decompress gzip output of INPUT
24#   COMPARE                 - Verify decompressed output is the same as input
25#   SUCCESS_EXIT            - List of successful exit codes (default: 0, ie: 0;1)
26
27if(TARGET)
28    set(COMPRESS_TARGET ${TARGET})
29    set(DECOMPRESS_TARGET ${TARGET})
30endif()
31
32if(NOT DEFINED INPUT OR NOT DEFINED COMPRESS_TARGET OR NOT DEFINED DECOMPRESS_TARGET)
33    message(FATAL_ERROR "Compress test arguments missing")
34endif()
35
36# Set default values
37if(NOT DEFINED COMPARE)
38    set(COMPARE ON)
39endif()
40if(NOT DEFINED COMPRESS_ARGS)
41    set(COMPRESS_ARGS -c -k)
42endif()
43if(NOT DEFINED DECOMPRESS_ARGS)
44    set(DECOMPRESS_ARGS -d -c)
45endif()
46if(NOT DEFINED GZIP_VERIFY)
47    set(GZIP_VERIFY ON)
48endif()
49if(NOT DEFINED SUCCESS_EXIT)
50    set(SUCCESS_EXIT 0)
51endif()
52
53# Use test name from input file name
54if(NOT DEFINED TEST_NAME)
55    get_filename_component(TEST_NAME "${INPUT}" NAME)
56endif()
57
58# Generate unique output path so multiple tests can be executed at the same time
59string(RANDOM LENGTH 6 UNIQUE_ID)
60string(REPLACE "." "-" TEST_NAME "${TEST_NAME}")
61set(OUTPUT_BASE "${CMAKE_CURRENT_BINARY_DIR}/Testing/Temporary/${TEST_NAME}-${UNIQUE_ID}")
62
63# Ensure directory exists for output files
64get_filename_component(OUTPUT_DIR "${OUTPUT_BASE}" DIRECTORY)
65file(MAKE_DIRECTORY "${OUTPUT_DIR}")
66
67# Cleanup temporary files
68macro(cleanup_always)
69    file(GLOB TEMP_FILES ${OUTPUT_BASE}*)
70    file(REMOVE ${TEMP_FILES})
71endmacro()
72# Clean up temporary files if not on CI
73macro(cleanup)
74    if(NOT DEFINED ENV{CI})
75        cleanup_always()
76    endif()
77endmacro()
78
79# Show differences between two files
80macro(diff src1 src2)
81    find_program(XXD xxd)
82    if(XXD)
83        find_program(DIFF diff)
84        if(DIFF)
85            set(XXD_COMMAND ${XXD} ${src1} ${src1}.hex)
86            execute_process(COMMAND ${XXD_COMMAND})
87            set(XXD_COMMAND ${XXD} ${src2} ${src2}.hex)
88            execute_process(COMMAND ${XXD_COMMAND})
89
90            set(DIFF_COMMAND ${DIFF} -u ${src1}.hex ${src2}.hex)
91            execute_process(COMMAND ${DIFF_COMMAND}
92                OUTPUT_FILE ${src2}.diff)
93
94            file(READ ${src2}.diff DIFF_OUTPUT)
95            message(STATUS "Diff:\n${DIFF_OUTPUT}")
96
97            if(NOT DEFINED ENV{CI})
98                file(REMOVE ${src1}.hex ${src2}.hex ${src2}.diff)
99            endif()
100        endif()
101    endif()
102endmacro()
103
104# Compress input file
105if(NOT EXISTS ${INPUT})
106    message(FATAL_ERROR "Cannot find compress input: ${INPUT}")
107endif()
108
109set(COMPRESS_COMMAND ${COMPRESS_TARGET} ${COMPRESS_ARGS})
110
111message(STATUS "Compress ${COMPRESS_COMMAND}")
112message(STATUS "  Input: ${INPUT}")
113message(STATUS "  Output: ${OUTPUT_BASE}.gz")
114
115execute_process(COMMAND ${CMAKE_COMMAND}
116    "-DCOMMAND=${COMPRESS_COMMAND}"
117    -DINPUT=${INPUT}
118    -DOUTPUT=${OUTPUT_BASE}.gz
119    "-DSUCCESS_EXIT=${SUCCESS_EXIT}"
120    -P ${CMAKE_CURRENT_LIST_DIR}/run-and-redirect.cmake
121    RESULT_VARIABLE CMD_RESULT)
122
123if(CMD_RESULT)
124    cleanup()
125    message(FATAL_ERROR "Compress failed: ${CMD_RESULT}")
126endif()
127
128# Decompress output
129if(NOT EXISTS ${OUTPUT_BASE}.gz)
130    cleanup()
131    message(FATAL_ERROR "Cannot find decompress input: ${OUTPUT_BASE}.gz")
132endif()
133
134set(DECOMPRESS_COMMAND ${DECOMPRESS_TARGET} ${DECOMPRESS_ARGS})
135
136message(STATUS "Decompress ${DECOMPRESS_COMMAND}")
137message(STATUS "  Input: ${OUTPUT_BASE}.gz")
138message(STATUS "  Output: ${OUTPUT_BASE}")
139
140execute_process(COMMAND ${CMAKE_COMMAND}
141    "-DCOMMAND=${DECOMPRESS_COMMAND}"
142    -DINPUT=${OUTPUT_BASE}.gz
143    -DOUTPUT=${OUTPUT_BASE}
144    "-DSUCCESS_EXIT=${SUCCESS_EXIT}"
145    -P ${CMAKE_CURRENT_LIST_DIR}/run-and-redirect.cmake
146    RESULT_VARIABLE CMD_RESULT)
147
148if(CMD_RESULT)
149    cleanup()
150    message(FATAL_ERROR "Decompress failed: ${CMD_RESULT}")
151endif()
152
153if(COMPARE)
154    # Compare decompressed output with original input file
155    execute_process(COMMAND ${CMAKE_COMMAND}
156        -E compare_files ${INPUT} ${OUTPUT_BASE}
157        RESULT_VARIABLE CMD_RESULT)
158
159    if(CMD_RESULT)
160        diff(${INPUT} ${OUTPUT_BASE})
161        cleanup()
162        message(FATAL_ERROR "Compare decompress failed: ${CMD_RESULT}")
163    endif()
164endif()
165
166if(GZIP_VERIFY AND NOT "${COMPRESS_ARGS}" MATCHES "-T")
167    # Transparent writing does not use gzip format
168    find_program(GZIP gzip)
169    if(GZIP)
170        if(NOT EXISTS ${OUTPUT_BASE}.gz)
171            cleanup()
172            message(FATAL_ERROR "Cannot find gzip decompress input: ${OUTPUT_BASE}.gz")
173        endif()
174
175        # Check gzip can decompress our compressed output
176        set(GZ_DECOMPRESS_COMMAND ${GZIP} --decompress)
177
178        message(STATUS "Gzip decompress ${GZ_DECOMPRESS_COMMAND}")
179        message(STATUS "  Input: ${OUTPUT_BASE}.gz")
180        message(STATUS "  Output: ${OUTPUT_BASE}-ungzip")
181
182        execute_process(COMMAND ${CMAKE_COMMAND}
183            "-DCOMMAND=${GZ_DECOMPRESS_COMMAND}"
184            -DINPUT=${OUTPUT_BASE}.gz
185            -DOUTPUT=${OUTPUT_BASE}-ungzip
186            "-DSUCCESS_EXIT=${SUCCESS_EXIT}"
187            -P ${CMAKE_CURRENT_LIST_DIR}/run-and-redirect.cmake
188            RESULT_VARIABLE CMD_RESULT)
189
190        if(CMD_RESULT)
191            cleanup()
192            message(FATAL_ERROR "Gzip decompress failed: ${CMD_RESULT}")
193        endif()
194
195        # Compare gzip output with original input file
196        execute_process(COMMAND ${CMAKE_COMMAND}
197            -E compare_files ${INPUT} ${OUTPUT_BASE}-ungzip
198            RESULT_VARIABLE CMD_RESULT)
199
200        if(CMD_RESULT)
201            diff(${INPUT} ${OUTPUT_BASE}-ungzip)
202            cleanup()
203            message(FATAL_ERROR "Compare gzip decompress failed: ${CMD_RESULT}")
204        endif()
205
206        # Compress input file with gzip
207        set(GZ_COMPRESS_COMMAND ${GZIP} --stdout)
208
209        message(STATUS "Gzip compress ${GZ_COMPRESS_COMMAND}")
210        message(STATUS "  Input: ${INPUT}")
211        message(STATUS "  Output: ${OUTPUT_BASE}-gzip.gz")
212
213        execute_process(COMMAND ${CMAKE_COMMAND}
214            "-DCOMMAND=${GZ_COMPRESS_COMMAND}"
215            -DINPUT=${INPUT}
216            -DOUTPUT=${OUTPUT_BASE}-gzip.gz
217            "-DSUCCESS_EXIT=${SUCCESS_EXIT}"
218            -P ${CMAKE_CURRENT_LIST_DIR}/run-and-redirect.cmake
219            RESULT_VARIABLE CMD_RESULT)
220
221        if(CMD_RESULT)
222            cleanup()
223            message(FATAL_ERROR "Gzip compress failed: ${CMD_RESULT}")
224        endif()
225
226        if(NOT EXISTS ${OUTPUT_BASE}-gzip.gz)
227            cleanup()
228            message(FATAL_ERROR "Cannot find decompress gzip input: ${OUTPUT_BASE}-gzip.gz")
229        endif()
230
231        message(STATUS "Decompress gzip ${DECOMPRESS_COMMAND}")
232        message(STATUS "  Input: ${OUTPUT_BASE}-gzip.gz")
233        message(STATUS "  Output: ${OUTPUT_BASE}-gzip")
234
235        # Check decompress target can handle gzip compressed output
236        execute_process(COMMAND ${CMAKE_COMMAND}
237            "-DCOMMAND=${DECOMPRESS_COMMAND}"
238            -DINPUT=${OUTPUT_BASE}-gzip.gz
239            -DOUTPUT=${OUTPUT_BASE}-gzip
240            "-DSUCCESS_EXIT=${SUCCESS_EXIT}"
241            -P ${CMAKE_CURRENT_LIST_DIR}/run-and-redirect.cmake
242            RESULT_VARIABLE CMD_RESULT)
243
244        if(CMD_RESULT)
245            cleanup()
246            message(FATAL_ERROR "Decompress gzip failed: ${CMD_RESULT}")
247        endif()
248
249        if(COMPARE)
250            # Compare original input file with gzip decompressed output
251            execute_process(COMMAND ${CMAKE_COMMAND}
252                -E compare_files ${INPUT} ${OUTPUT_BASE}-gzip
253                RESULT_VARIABLE CMD_RESULT)
254
255            if(CMD_RESULT)
256                diff(${INPUT} ${OUTPUT_BASE}-gzip)
257                cleanup()
258                message(FATAL_ERROR "Compare decompress gzip failed: ${CMD_RESULT}")
259            endif()
260        endif()
261    endif()
262endif()
263
264cleanup_always()
265