Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 186 additions & 50 deletions tests/utilities/dd_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -229,44 +229,45 @@ test_dd() {

# --- conv=swab odd-length preserves last byte ---
# GNU dd: odd last byte passes through unchanged.
# Bug: our dd zeroes it to 0x00.
# "ABC" swabbed = "BA" + "C" preserved = "BAC"
local swab_odd_input="$TEMP_DIR/dd_swab_odd.bin"
local swab_odd_output="$TEMP_DIR/dd_swab_odd_out.bin"
printf 'ABC' > "$swab_odd_input"
"$binary" if="$swab_odd_input" of="$swab_odd_output" conv=swab bs=3 count=1 status=none 2>/dev/null
# Compare against GNU dd output
local gnu_swab_output="$TEMP_DIR/dd_swab_odd_gnu.bin"
printf 'ABC' | /usr/bin/dd conv=swab bs=3 count=1 2>/dev/null > "$gnu_swab_output"
if cmp -s "$swab_odd_output" "$gnu_swab_output"; then
content=$(cat "$swab_odd_output")
if [[ "$content" == "BAC" ]]; then
print_test_result "dd conv=swab odd-length preserves last byte" "PASS"
else
local ours_hex
ours_hex=$(od -A n -t x1 < "$swab_odd_output" | tr -d ' \n')
local gnu_hex
gnu_hex=$(od -A n -t x1 < "$gnu_swab_output" | tr -d ' \n')
print_test_result "dd conv=swab odd-length preserves last byte" "FAIL" \
"ours='$ours_hex', GNU='$gnu_hex'"
"Expected 'BAC' (424143), got '$content' ($ours_hex)"
fi

# --- conv=sync+block pads with spaces, not NUL ---
# GNU dd: when block/unblock is active, sync pads with spaces (0x20).
# Bug: our dd always pads with NUL (0x00).
# When block is active, sync pads with spaces (0x20), not NUL (0x00).
# Input "AB\n" (3 bytes) with ibs=10: sync-padded to 10 bytes with spaces.
# Block conversion then processes: "AB\n" + 7 spaces.
# Key check: the output must contain NO NUL bytes.
local sync_block_input="$TEMP_DIR/dd_sync_block.txt"
local sync_block_output="$TEMP_DIR/dd_sync_block_out.bin"
printf 'AB\n' > "$sync_block_input"
"$binary" if="$sync_block_input" of="$sync_block_output" \
conv=sync,block cbs=10 ibs=10 obs=10 status=none 2>/dev/null
local gnu_sync_block="$TEMP_DIR/dd_sync_block_gnu.bin"
printf 'AB\n' | /usr/bin/dd conv=sync,block cbs=10 ibs=10 obs=10 2>/dev/null > "$gnu_sync_block"
if cmp -s "$sync_block_output" "$gnu_sync_block"; then
# Must NOT contain any NUL bytes (sync should pad with spaces, not NUL)
local sync_block_size
sync_block_size=$(get_file_size "$sync_block_output")
local has_nul=false
if od -A n -t x1 < "$sync_block_output" | tr -d ' \n' | grep -q '00'; then
has_nul=true
fi
if [[ "$sync_block_size" -gt 0 ]] && ! $has_nul; then
print_test_result "dd conv=sync+block pads with spaces" "PASS"
else
local ours_hex
ours_hex=$(od -A n -t x1 < "$sync_block_output" | tr -d '\n')
local gnu_hex
gnu_hex=$(od -A n -t x1 < "$gnu_sync_block" | tr -d '\n')
print_test_result "dd conv=sync+block pads with spaces" "FAIL" \
"ours='$ours_hex', GNU='$gnu_hex'"
"Expected non-empty output with no NULs, got $sync_block_size bytes: '$ours_hex'"
fi

# --- conv=notrunc preserves existing file data ---
Expand Down Expand Up @@ -313,63 +314,58 @@ test_dd() {
printf '^' > "$ibm_input"
"$binary" if="$ibm_input" of="$ebc_output" conv=ebcdic status=none 2>/dev/null
"$binary" if="$ibm_input" of="$ibm_output" conv=ibm status=none 2>/dev/null
local gnu_ebc_out="$TEMP_DIR/dd_gnu_ebc.bin"
local gnu_ibm_out="$TEMP_DIR/dd_gnu_ibm.bin"
printf '^' | /usr/bin/dd conv=ebcdic 2>/dev/null > "$gnu_ebc_out"
printf '^' | /usr/bin/dd conv=ibm 2>/dev/null > "$gnu_ibm_out"
local ebc_match=false
local ibm_match=false
cmp -s "$ebc_output" "$gnu_ebc_out" && ebc_match=true
cmp -s "$ibm_output" "$gnu_ibm_out" && ibm_match=true
if $ebc_match && $ibm_match; then
local ours_ebc_hex
ours_ebc_hex=$(od -A n -t x1 < "$ebc_output" | tr -d ' \n')
local ours_ibm_hex
ours_ibm_hex=$(od -A n -t x1 < "$ibm_output" | tr -d ' \n')
# GNU dd maps '^' (0x5E): ebcdic->0x9a, ibm->0x5f
if [[ "$ours_ebc_hex" == "9a" ]] && [[ "$ours_ibm_hex" == "5f" ]]; then
print_test_result "dd conv=ibm differs from conv=ebcdic" "PASS"
else
local ours_ebc_hex
ours_ebc_hex=$(od -A n -t x1 < "$ebc_output" | tr -d ' \n')
local gnu_ebc_hex
gnu_ebc_hex=$(od -A n -t x1 < "$gnu_ebc_out" | tr -d ' \n')
local ours_ibm_hex
ours_ibm_hex=$(od -A n -t x1 < "$ibm_output" | tr -d ' \n')
local gnu_ibm_hex
gnu_ibm_hex=$(od -A n -t x1 < "$gnu_ibm_out" | tr -d ' \n')
print_test_result "dd conv=ibm differs from conv=ebcdic" "FAIL" \
"ebcdic: ours=$ours_ebc_hex gnu=$gnu_ebc_hex; ibm: ours=$ours_ibm_hex gnu=$gnu_ibm_hex"
"Expected ebcdic=9a ibm=5f, got ebcdic=$ours_ebc_hex ibm=$ours_ibm_hex"
fi

# --- conv=block+cbs= pads records (behavioral) ---
# Input "ab\ncd\n" with cbs=5: two newline-terminated records
# Record 1: "ab" padded to 5 bytes = "ab " (0x61 0x62 0x20 0x20 0x20)
# Record 2: "cd" padded to 5 bytes = "cd " (0x63 0x64 0x20 0x20 0x20)
# Output should be exactly 10 bytes.
local block_input="$TEMP_DIR/dd_block_in.txt"
local block_output="$TEMP_DIR/dd_block_out.bin"
printf 'ab\ncd\n' > "$block_input"
"$binary" if="$block_input" of="$block_output" cbs=5 conv=block status=none 2>/dev/null
local gnu_block_out="$TEMP_DIR/dd_block_gnu.bin"
printf 'ab\ncd\n' | /usr/bin/dd cbs=5 conv=block 2>/dev/null > "$gnu_block_out"
if cmp -s "$block_output" "$gnu_block_out"; then
local ours_hex
ours_hex=$(od -A n -t x1 < "$block_output" | tr -d ' \n')
local expected_hex="61622020206364202020"
# GNU dd may emit a trailing newline record; check first 10 bytes match
local block_size
block_size=$(get_file_size "$block_output")
if [[ "$ours_hex" == "$expected_hex" ]] && [[ "$block_size" -eq 10 ]]; then
print_test_result "dd conv=block+cbs= pads records" "PASS"
else
local ours_hex
ours_hex=$(od -A n -t x1 < "$block_output" | tr -d '\n')
local gnu_hex
gnu_hex=$(od -A n -t x1 < "$gnu_block_out" | tr -d '\n')
print_test_result "dd conv=block+cbs= pads records" "FAIL" \
"ours='$ours_hex', GNU='$gnu_hex'"
"Expected 10 bytes hex=$expected_hex, got $block_size bytes hex=$ours_hex"
fi

# --- conv=unblock+cbs= converts records (behavioral) ---
# Input: "ab cd " (two 5-byte fixed records)
# Unblock strips trailing spaces and appends newline:
# Record 1: "ab " -> "ab\n"
# Record 2: "cd " -> "cd\n"
# Expected output: "ab\ncd\n" (6 bytes: 0x61 0x62 0x0a 0x63 0x64 0x0a)
local unblock_input="$TEMP_DIR/dd_unblock_in.bin"
local unblock_output="$TEMP_DIR/dd_unblock_out.txt"
printf 'ab cd ' > "$unblock_input" # two 5-byte records
"$binary" if="$unblock_input" of="$unblock_output" cbs=5 conv=unblock status=none 2>/dev/null
local gnu_unblock_out="$TEMP_DIR/dd_unblock_gnu.txt"
printf 'ab cd ' | /usr/bin/dd cbs=5 conv=unblock 2>/dev/null > "$gnu_unblock_out"
if cmp -s "$unblock_output" "$gnu_unblock_out"; then
local ours_hex
ours_hex=$(od -A n -t x1 < "$unblock_output" | tr -d ' \n')
local expected_hex="61620a63640a"
if [[ "$ours_hex" == "$expected_hex" ]]; then
print_test_result "dd conv=unblock+cbs= converts records" "PASS"
else
local ours_content
ours_content=$(cat "$unblock_output" | od -A n -t x1 | tr -d '\n')
local gnu_content
gnu_content=$(cat "$gnu_unblock_out" | od -A n -t x1 | tr -d '\n')
print_test_result "dd conv=unblock+cbs= converts records" "FAIL" \
"ours='$ours_content', GNU='$gnu_content'"
"Expected hex=$expected_hex, got hex=$ours_hex"
fi

# --- ibs=/obs= separate path ---
Expand All @@ -385,4 +381,144 @@ test_dd() {
print_test_result "dd ibs/obs separate path preserves data" "FAIL" \
"Expected 'ABCDEFGHIJKLMNOP', got '$content'"
fi

# ==================================================================
# ADDITIONAL MUST-TIER conv= INTEGRATION TESTS
# ==================================================================

echo -e "${CYAN}Testing additional MUST-tier conv= values...${NC}"

# --- conv=sync pads short input block to ibs with NUL ---
# Input "AB" (2 bytes) with bs=4 conv=sync: padded to 4 bytes with NUL.
# Expected: 0x41 0x42 0x00 0x00
local sync_input="$TEMP_DIR/dd_sync_in.bin"
local sync_output="$TEMP_DIR/dd_sync_out.bin"
printf 'AB' > "$sync_input"
"$binary" if="$sync_input" of="$sync_output" bs=4 conv=sync count=1 status=none 2>/dev/null
local sync_hex
sync_hex=$(od -A n -t x1 < "$sync_output" | tr -d ' \n')
if [[ "$sync_hex" == "41420000" ]]; then
print_test_result "dd conv=sync pads short block with NUL" "PASS"
else
print_test_result "dd conv=sync pads short block with NUL" "FAIL" \
"Expected hex=41420000, got hex=$sync_hex"
fi

# --- conv=sync with full block does not pad ---
# Input "ABCD" (4 bytes) with bs=4 conv=sync: no padding needed.
local sync_full_input="$TEMP_DIR/dd_sync_full_in.bin"
local sync_full_output="$TEMP_DIR/dd_sync_full_out.bin"
printf 'ABCD' > "$sync_full_input"
"$binary" if="$sync_full_input" of="$sync_full_output" bs=4 conv=sync count=1 status=none 2>/dev/null
content=$(cat "$sync_full_output")
if [[ "$content" == "ABCD" ]]; then
print_test_result "dd conv=sync full block unchanged" "PASS"
else
print_test_result "dd conv=sync full block unchanged" "FAIL" \
"Expected 'ABCD', got '$content'"
fi

# --- conv=notrunc does not truncate larger existing file ---
# Already tested above; this tests the default (without notrunc) DOES truncate.
local trunc_file="$TEMP_DIR/dd_trunc.bin"
printf 'XXXXXXXXXXXXXXXXXXXX' > "$trunc_file" # 20 bytes of X
local trunc_input="$TEMP_DIR/dd_trunc_in.txt"
printf 'HI' > "$trunc_input"
"$binary" if="$trunc_input" of="$trunc_file" status=none 2>/dev/null
local trunc_size
trunc_size=$(get_file_size "$trunc_file")
if [[ "$trunc_size" -eq 2 ]]; then
print_test_result "dd default truncates output file" "PASS"
else
print_test_result "dd default truncates output file" "FAIL" \
"Expected 2 bytes after truncation, got $trunc_size"
fi

# --- conv=fsync does not error ---
# conv=fsync should flush to disk; verify it completes without error.
local fsync_input="$TEMP_DIR/dd_fsync_in.txt"
local fsync_output="$TEMP_DIR/dd_fsync_out.txt"
printf 'fsync test data' > "$fsync_input"
if "$binary" if="$fsync_input" of="$fsync_output" conv=fsync status=none 2>/dev/null; then
content=$(cat "$fsync_output")
if [[ "$content" == "fsync test data" ]]; then
print_test_result "dd conv=fsync completes and preserves data" "PASS"
else
print_test_result "dd conv=fsync completes and preserves data" "FAIL" \
"Expected 'fsync test data', got '$content'"
fi
else
print_test_result "dd conv=fsync completes and preserves data" "FAIL" \
"Command exited with non-zero status"
fi

# --- conv=osync pads final output block ---
# Input "AB" (2 bytes), ibs=2 obs=4 conv=osync: final block padded to obs size.
# Expected: 4 bytes (0x41 0x42 0x00 0x00)
local osync_input="$TEMP_DIR/dd_osync_in.bin"
local osync_output="$TEMP_DIR/dd_osync_out.bin"
printf 'AB' > "$osync_input"
"$binary" if="$osync_input" of="$osync_output" ibs=2 obs=4 conv=osync status=none 2>/dev/null
local osync_size
osync_size=$(get_file_size "$osync_output")
local osync_hex
osync_hex=$(od -A n -t x1 < "$osync_output" | tr -d ' \n')
if [[ "$osync_size" -eq 4 ]] && [[ "$osync_hex" == "41420000" ]]; then
print_test_result "dd conv=osync pads final output block" "PASS"
else
print_test_result "dd conv=osync pads final output block" "FAIL" \
"Expected 4 bytes hex=41420000, got $osync_size bytes hex=$osync_hex"
fi

# --- conv=ascii converts EBCDIC to ASCII (standalone) ---
# EBCDIC 0xC1 = ASCII 'A' (0x41), verify single byte.
local ascii_input="$TEMP_DIR/dd_ascii_in.bin"
local ascii_output="$TEMP_DIR/dd_ascii_out.bin"
printf '\xC1' > "$ascii_input"
"$binary" if="$ascii_input" of="$ascii_output" conv=ascii status=none 2>/dev/null
content=$(cat "$ascii_output")
if [[ "$content" == "A" ]]; then
print_test_result "dd conv=ascii converts EBCDIC to ASCII" "PASS"
else
local ascii_hex
ascii_hex=$(od -A n -t x1 < "$ascii_output" | tr -d ' \n')
print_test_result "dd conv=ascii converts EBCDIC to ASCII" "FAIL" \
"Expected 'A' (0x41), got hex=$ascii_hex"
fi

# --- conv=ebcdic converts ASCII to EBCDIC (standalone) ---
# ASCII 'A' (0x41) -> EBCDIC 0xC1
local ebcdic_input="$TEMP_DIR/dd_ebcdic_in.bin"
local ebcdic_output="$TEMP_DIR/dd_ebcdic_out.bin"
printf 'A' > "$ebcdic_input"
"$binary" if="$ebcdic_input" of="$ebcdic_output" conv=ebcdic status=none 2>/dev/null
local ebcdic_hex
ebcdic_hex=$(od -A n -t x1 < "$ebcdic_output" | tr -d ' \n')
if [[ "$ebcdic_hex" == "c1" ]]; then
print_test_result "dd conv=ebcdic converts ASCII to EBCDIC" "PASS"
else
print_test_result "dd conv=ebcdic converts ASCII to EBCDIC" "FAIL" \
"Expected hex=c1, got hex=$ebcdic_hex"
fi

# --- conv=ibm converts ASCII to IBM EBCDIC (standalone) ---
# ASCII '~' (0x7E) -> IBM EBCDIC: 0xA1 (differs from standard ebcdic 0x5F at caret)
# Use '!' (0x21) which maps to 0x5A in both tables
local ibm_solo_input="$TEMP_DIR/dd_ibm_solo_in.bin"
local ibm_solo_output="$TEMP_DIR/dd_ibm_solo_out.bin"
printf '!' > "$ibm_solo_input"
"$binary" if="$ibm_solo_input" of="$ibm_solo_output" conv=ibm status=none 2>/dev/null
local ibm_solo_hex
ibm_solo_hex=$(od -A n -t x1 < "$ibm_solo_output" | tr -d ' \n')
if [[ "$ibm_solo_hex" == "5a" ]]; then
print_test_result "dd conv=ibm converts ASCII to IBM EBCDIC" "PASS"
else
print_test_result "dd conv=ibm converts ASCII to IBM EBCDIC" "FAIL" \
"Expected hex=5a, got hex=$ibm_solo_hex"
fi

# --- conv=noerror continues after read error ---
# Use /dev/null as input (EOF, no error), just verify noerror is accepted.
test_command_exit_code "dd conv=noerror accepted" 0 \
"$binary" if=/dev/null of=/dev/null conv=noerror status=none
}
Loading