#!/bin/bash
#
# Copyright 2008 Google Inc.
# Author: Lincoln Smith
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This script tests the correctness of the vcdiff command-line executable.
# If you add a new test here, please add the same test to the Windows script
# vsprojects/vcdiff_test.bat.
#
# The caller should set the environment variable $srcdir to the root directory
# of the open-vcdiff package.  ($srcdir is automatically provided by Automake
# when this script is run by "make check".)

# Find input files
VCDIFF=./vcdiff
# These options are only needed for the encoder;
# the decoder will recognize the interleaved and checksum formats
# without needing to specify any options.
VCD_OPTIONS="-interleaved -checksum"
DICTIONARY_FILE=$srcdir/testdata/configure.ac.v0.1
TARGET_FILE=$srcdir/testdata/configure.ac.v0.2
TEST_TMPDIR=${TMPDIR-/tmp}
DELTA_FILE=$TEST_TMPDIR/configure.ac.vcdiff
OUTPUT_TARGET_FILE=$TEST_TMPDIR/configure.ac.output
MALICIOUS_ENCODING=$srcdir/testdata/allocates_4gb.vcdiff

# vcdiff with no arguments shows usage information & error result
$VCDIFF \
&& { echo "vcdiff with no arguments should fail, but succeeded"; \
     exit 1; }
echo "Test 1 ok";

# vcdiff with three arguments but without "encode" or "decode"
# shows usage information & error result
$VCDIFF $VCD_OPTIONS \
        -dictionary $DICTIONARY_FILE -target $TARGET_FILE -delta $DELTA_FILE \
&& { echo "vcdiff without operation argument should fail, but succeeded"; \
     exit 1; }
echo "Test 2 ok";

# vcdiff with all three arguments.  Verify that output file matches target file
$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target $TARGET_FILE \
               -delta $DELTA_FILE \
|| { echo "Encode with three arguments failed"; \
     exit 1; }
$VCDIFF decode -dictionary $DICTIONARY_FILE \
               -delta $DELTA_FILE \
               -target $OUTPUT_TARGET_FILE \
|| { echo "Decode with three arguments failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original"; \
     exit 1; }
echo "Test 3 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

# vcdiff using stdin/stdout.  Verify that output file matches target file
{ $VCDIFF $VCD_OPTIONS \
          encode -dictionary $DICTIONARY_FILE \
                 < $TARGET_FILE \
                 > $DELTA_FILE; } \
|| { echo "Encode using stdin/stdout failed"; \
     exit 1; }
{ $VCDIFF decode -dictionary $DICTIONARY_FILE \
                 < $DELTA_FILE \
                 > $OUTPUT_TARGET_FILE; } \
|| { echo "Decode using stdin/stdout failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original"; \
     exit 1; }
echo "Test 4 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

# vcdiff with mixed stdin/stdout.
{ $VCDIFF $VCD_OPTIONS \
          encode -dictionary $DICTIONARY_FILE \
                 -target $TARGET_FILE \
                 > $DELTA_FILE; } \
|| { echo "Encode with mixed arguments failed"; \
     exit 1; }
{ $VCDIFF decode -dictionary $DICTIONARY_FILE \
                 -delta $DELTA_FILE \
                 > $OUTPUT_TARGET_FILE; } \
|| { echo "Decode with mixed arguments failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original"; \
     exit 1; }
echo "Test 5 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

{ $VCDIFF $VCD_OPTIONS \
          encode -dictionary $DICTIONARY_FILE \
                 < $TARGET_FILE \
                 -delta $DELTA_FILE; } \
|| { echo "Encode with mixed arguments failed"; \
     exit 1; }
{ $VCDIFF decode -dictionary $DICTIONARY_FILE \
                 < $DELTA_FILE \
                 -target $OUTPUT_TARGET_FILE; } \
|| { echo "Decode with mixed arguments failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original"; \
     exit 1; }
echo "Test 6 ok";

rm $OUTPUT_TARGET_FILE
# Don't remove $DELTA_FILE; use it for the next test

# If using the wrong dictionary, and dictionary is smaller than the original
# dictionary, vcdiff will spot the mistake and return an error.  (It can't
# detect the case where the wrong dictionary is larger than the right one.)
$VCDIFF decode -dictionary $TARGET_FILE \
               -delta $DELTA_FILE \
               -target $OUTPUT_TARGET_FILE \
&& { echo "Decode using larger dictionary should fail, but succeeded"; \
     exit 1; }
echo "Test 7 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

# "vcdiff test" with all three arguments.
$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
|| { echo "vcdiff test with three arguments failed"; \
     exit 1; }
echo "Test 8 ok";

rm $DELTA_FILE

# Dictionary file not found.
$VCDIFF $VCD_OPTIONS \
        encode -dictionary $TEST_TMPDIR/nonexistent_file \
               -target $TARGET_FILE \
               -delta $DELTA_FILE \
&& { echo "vcdiff with missing dictionary file should fail, but succeeded"; \
     exit 1; }
echo "Test 9 ok";

# Target file not found.
$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target $TEST_TMPDIR/nonexistent_file \
               -delta $DELTA_FILE \
&& { echo "vcdiff with missing target file should fail, but succeeded"; \
     exit 1; }
echo "Test 10 ok";

# Delta file not found.
$VCDIFF decode -dictionary $DICTIONARY_FILE \
               -delta $TEST_TMPDIR/nonexistent_file \
               -target $OUTPUT_TARGET_FILE \
&& { echo "vcdiff with missing delta file should fail, but succeeded"; \
     exit 1; }
echo "Test 11 ok";

# Try traversing an infinite loop of symbolic links.
ln -s $TEST_TMPDIR/infinite_loop1 $TEST_TMPDIR/infinite_loop2
ln -s $TEST_TMPDIR/infinite_loop2 $TEST_TMPDIR/infinite_loop1
$VCDIFF $VCD_OPTIONS \
        encode -dictionary $TEST_TMPDIR/infinite_loop1 \
               -target $TEST_TMPDIR/infinite_loop2 \
               -delta $DELTA_FILE \
&& { echo "vcdiff with symbolic link loop should fail, but succeeded"; \
     exit 1; }
echo "Test 12 ok";

rm $TEST_TMPDIR/infinite_loop1 $TEST_TMPDIR/infinite_loop2

# Test using -stats flag
$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target $TARGET_FILE \
               -delta $DELTA_FILE \
               -stats \
|| { echo "Encode with -stats failed"; \
     exit 1; }
$VCDIFF -stats \
        decode -dictionary $DICTIONARY_FILE \
               -delta $DELTA_FILE \
               -target $OUTPUT_TARGET_FILE \
|| { echo "Decode with -stats failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original"; \
     exit 1; }
echo "Test 13 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

# Using /dev/null as dictionary should work, but (because dictionary is empty)
# it will not produce a small delta file.
$VCDIFF $VCD_OPTIONS \
        test -dictionary /dev/null \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
             -stats \
|| { echo "vcdiff test with /dev/null as dictionary failed"; \
     exit 1; }
echo "Test 14 ok";

rm $DELTA_FILE

# Using /dev/kmem as dictionary or target should produce an error
# (permission denied, or too large, or special file type)
$VCDIFF $VCD_OPTIONS \
        encode -dictionary /dev/kmem \
               -target $TARGET_FILE \
               -delta $DELTA_FILE \
&& { echo "vcdiff with /dev/kmem as dictionary should fail, but succeeded"; \
     exit 1; }
echo "Test 15 ok";

$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target /dev/kmem \
               -delta $DELTA_FILE \
&& { echo "vcdiff with /dev/kmem as target should fail, but succeeded"; \
     exit 1; }
echo "Test 16 ok";

# Decode using something that isn't a delta file
$VCDIFF decode -dictionary $DICTIONARY_FILE \
               -delta /etc/fstab \
               -target $OUTPUT_TARGET_FILE \
&& { echo "vcdiff with invalid delta file should fail, but succeeded"; \
     exit 1; }
echo "Test 17 ok";

$VCDIFF $VCD_OPTIONS \
        encode -target $TARGET_FILE \
               -delta $DELTA_FILE \
               -dictionary \
&& { echo "-dictionary option with no file name should fail, but succeeded"; \
     exit 1; }
echo "Test 18 ok";

$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -delta $DELTA_FILE \
               -target \
&& { echo "-target option with no file name should fail, but succeeded"; \
     exit 1; }
echo "Test 19 ok";

$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target $TARGET_FILE \
               -delta \
&& { echo "-delta option with no file name should fail, but succeeded"; \
     exit 1; }
echo "Test 20 ok";

$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target $TARGET_FILE \
               -delta $DELTA_FILE \
               -buffersize \
&& { echo "-buffersize option with no argument should fail, but succeeded"; \
     exit 1; }
echo "Test 21 ok";

# Using -buffersize=1 should still work.
$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
             -buffersize 1 \
             -stats \
|| { echo "vcdiff test with -buffersize=1 failed"; \
     exit 1; }
echo "Test 22 ok";

rm $DELTA_FILE

# Using -buffersize=1 with stdin/stdout means that vcdiff
# will create a separate target window for each byte read.
{ $VCDIFF encode -dictionary $DICTIONARY_FILE \
                 -buffersize 1 \
                 -stats \
                 < $TARGET_FILE \
                 > $DELTA_FILE; } \
|| { echo "Encode using stdin/stdout with -buffersize=1 failed"; \
     exit 1; }
{ $VCDIFF decode -dictionary $DICTIONARY_FILE \
                 -buffersize 1 \
                 -stats \
                 < $DELTA_FILE \
                 > $OUTPUT_TARGET_FILE; } \
|| { echo "Decode using stdin/stdout with -buffersize=1 failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original with -buffersize=1"; \
     exit 1; }
echo "Test 23 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

# Using -buffersize=0 should fail.
$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
             -buffersize 0 \
&& { echo "vcdiff test with -buffersize=0 should fail, but succeeded"; \
     exit 1; }
echo "Test 24 ok";

rm $DELTA_FILE

# Using -buffersize=128M (larger than default maximum) should still work.
$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
             -buffersize 134217728 \
             -stats \
|| { echo "vcdiff test with -buffersize=128M failed"; \
     exit 1; }
echo "Test 25 ok";

rm $DELTA_FILE

$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
             -froobish \
&& { echo "vdiff test with unrecognized option should fail, but succeeded"; \
     exit 1; }
echo "Test 26 ok";

$VCDIFF $VCD_OPTIONS \
        encode -target $TARGET_FILE \
               -delta $DELTA_FILE \
&& { echo "encode with no dictionary option should fail, but succeeded"; \
     exit 1; }
echo "Test 27 ok";

$VCDIFF decode -target $TARGET_FILE \
               -delta $DELTA_FILE \
&& { echo "decode with no dictionary option should fail, but succeeded"; \
     exit 1; }
echo "Test 28 ok";

# Remove -interleaved and -checksum options
{ $VCDIFF encode -dictionary $DICTIONARY_FILE \
                 < $TARGET_FILE \
                 > $DELTA_FILE; } \
|| { echo "Encode without -interleaved and -checksum options failed"; \
     exit 1; }
{ $VCDIFF decode -dictionary $DICTIONARY_FILE \
                 < $DELTA_FILE \
                 > $OUTPUT_TARGET_FILE; } \
|| { echo "Decode non-interleaved output failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original with -interleaved"; \
     exit 1; }
echo "Test 29 ok";

# -target_matches option
{ $VCDIFF encode -dictionary $DICTIONARY_FILE \
                 -target_matches \
                 -stats \
                 < $TARGET_FILE \
                 > $DELTA_FILE; } \
|| { echo "Encode with -target_matches option failed"; \
     exit 1; }
# The decode operation ignores the -target_matches option.
{ $VCDIFF decode -dictionary $DICTIONARY_FILE \
                 < $DELTA_FILE \
                 > $OUTPUT_TARGET_FILE; } \
|| { echo "Decode output failed with -target_matches"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original with -target_matches"; \
     exit 1; }
echo "Test 30 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

$VCDIFF $VCD_OPTIONS \
        dencode -dictionary $DICTIONARY_FILE \
                -target $TARGET_FILE \
                -delta $DELTA_FILE \
&& { echo "vdiff with unrecognized action should fail, but succeeded"; \
     exit 1; }
echo "Test 31 ok";

$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
&& { echo "vdiff test without delta option should fail, but succeeded"; \
     exit 1; }
echo "Test 32 ok";

$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -delta $DELTA_FILE \
&& { echo "vdiff test without target option should fail, but succeeded"; \
     exit 1; }
echo "Test 33 ok";

# open-vcdiff bug 8 (http://code.google.com/p/open-vcdiff/issues/detail?id=8)
# A malicious encoding that tries to produce a 4GB target file made up of 64
# windows, each window having a size of 64MB.
# Limit memory usage to 256MB per process, so the test doesn't take forever
# to run out of memory.
OLD_ULIMIT=$(ulimit -v)
echo "Old ulimit: $OLD_ULIMIT"
ulimit -S -v 262144
echo "New ulimit: $(ulimit -v)"

$VCDIFF $VCD_OPTIONS \
    decode -dictionary $DICTIONARY_FILE \
           -delta $MALICIOUS_ENCODING \
           -target /dev/null \
           -max_target_file_size=65536 \
&& { echo "Decoding malicious file should fail, but succeeded"; \
     exit 1; }
echo "Test 34 ok";

$VCDIFF $VCD_OPTIONS \
    decode -dictionary $DICTIONARY_FILE \
           -delta $MALICIOUS_ENCODING \
           -target /dev/null \
           -max_target_window_size=65536 \
&& { echo "Decoding malicious file should fail, but succeeded"; \
     exit 1; }
echo "Test 35 ok";

ulimit -S -v $OLD_ULIMIT

# Decoding a small target with the -max_target_file_size option should succeed.
$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
             -max_target_file_size=65536 \
|| { echo "vcdiff test with -max_target_file_size failed"; \
     exit 1; }
echo "Test 36 ok";

# Decoding a small target with -max_target_window_size option should succeed.
$VCDIFF $VCD_OPTIONS \
        test -dictionary $DICTIONARY_FILE \
             -target $TARGET_FILE \
             -delta $DELTA_FILE \
             -max_target_window_size=65536 \
|| { echo "vcdiff test with -max_target_window_size failed"; \
     exit 1; }
echo "Test 37 ok";

rm $DELTA_FILE

# Test using -allow_vcd_target=false
$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target $TARGET_FILE \
               -delta $DELTA_FILE \
               -allow_vcd_target=false \
|| { echo "Encode with -allow_vcd_target=false failed"; \
     exit 1; }
$VCDIFF $VCD_OPTIONS \
        decode -dictionary $DICTIONARY_FILE \
               -delta $DELTA_FILE \
               -target $OUTPUT_TARGET_FILE \
               -allow_vcd_target=false \
|| { echo "Decode with -allow_vcd_target=false failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original"; \
     exit 1; }
echo "Test 38 ok";

rm $DELTA_FILE
rm $OUTPUT_TARGET_FILE

# Test using -allow_vcd_target=true
$VCDIFF $VCD_OPTIONS \
        encode -dictionary $DICTIONARY_FILE \
               -target $TARGET_FILE \
               -delta $DELTA_FILE \
               -allow_vcd_target=true \
|| { echo "Encode with -allow_vcd_target=true failed"; \
     exit 1; }
$VCDIFF $VCD_OPTIONS \
        decode -dictionary $DICTIONARY_FILE \
               -delta $DELTA_FILE \
               -target $OUTPUT_TARGET_FILE \
               -allow_vcd_target=true \
|| { echo "Decode with -allow_vcd_target=true failed"; \
     exit 1; }
cmp $TARGET_FILE $OUTPUT_TARGET_FILE \
|| { echo "Decoded target does not match original"; \
     exit 1; }
echo "Test 39 ok";

echo "PASS"