From 38970c75bdd00b0ee32dcd6000dc9d1f816bffca Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 19 Oct 2014 21:05:51 +0200 Subject: [PATCH] tests: Port bash tests to perl This integrates the tests written in bash into the existing perl infrastructure. --- encfs/tests.t | 150 +++++++++++++++++++++++++++++++---- tests/default.xml | 37 --------- tests/mac.xml | 37 --------- tests/run.sh | 109 ------------------------- tests/test_corruption.sh | 31 -------- tests/test_file_expansion.sh | 40 ---------- 6 files changed, 136 insertions(+), 268 deletions(-) delete mode 100644 tests/default.xml delete mode 100644 tests/mac.xml delete mode 100755 tests/run.sh delete mode 100644 tests/test_corruption.sh delete mode 100644 tests/test_file_expansion.sh diff --git a/encfs/tests.t b/encfs/tests.t index aa894cf..1d18cfe 100644 --- a/encfs/tests.t +++ b/encfs/tests.t @@ -2,15 +2,15 @@ use Test::More qw( no_plan ); use File::Path; +use File::Copy; use IO::Handle; -use Digest::MD5; +use Digest::MD5 qw(md5_hex); my $tempDir = $ENV{'TMPDIR'} || "/tmp"; my $raw = "$tempDir/crypt-raw-$$"; my $crypt = "$tempDir/crypt-$$"; - # test filesystem in standard config mode &runTests('standard'); @@ -18,7 +18,7 @@ my $crypt = "$tempDir/crypt-$$"; &runTests('paranoia'); - +# Wrapper function - runs all tests in the specified mode sub runTests { my $mode = shift; @@ -31,6 +31,7 @@ sub runTests { &mount("--paranoia"); $hardlinks = 0; # no hardlinks in paranoia mode + &corruption; } else { die "invalid test mode"; @@ -41,10 +42,66 @@ sub runTests &links($hardlinks); &truncate; &renames; + &internalModification; + &grow; &cleanup; } +# Test Corruption +# Modify the encrypted file and verify that the MAC check detects it +sub corruption +{ + ok( open(OUT, "+> $crypt/corrupt") && print(OUT "12345678901234567890") + && close(OUT), "create corruption-test file" ); + + + $e = encName("corrupt"); + ok( open(OUT, ">> $raw/$e") && print(OUT "garbage") && close(OUT), + "corrupting raw file"); + + ok( open(IN, "< $crypt/corrupt"), "open corrupted file"); + my $content; + $result = read(IN, $content, 20); + ok(! defined $result, "corrupted file with MAC returns read error: $!"); +} + +# Test internal modification +# Create a file of fixed size and overwrite data at different offsets +# (like a database would do) +sub internalModification +{ + $ofile = "$tempDir/crypt-internal-$$"; + qx(dd if=/dev/urandom of=$ofile bs=2k count=2 2> /dev/null); + ok(copy($ofile, "$crypt/internal"), "copying crypt-internal file"); + + open(my $out1, "+<", "$crypt/internal"); + open(my $out2, "+<", $ofile); + + @fhs = ($out1, $out2); + + $ori = md5fh($out1); + $b = md5fh($out2); + + ok( $ori eq $b, "random file md5 matches"); + + my @offsets = (10, 30, 1020, 1200); + foreach my $o (@offsets) + { + foreach my $fh(@fhs) { + seek($fh, $o, 0); + print($fh "garbagegarbagegarbagegarbagegarbage"); + } + $a=md5fh($out1); + $b=md5fh($out2); + ok( ($a eq $b) && ($a ne $ori), "internal modification at $o"); + } + + close($out1); + close($out2); +} + +# Test renames sub renames { ok( open(F, ">$crypt/orig-name") && close F, "create file for rename test"); @@ -73,6 +130,7 @@ sub renames is( (stat "$crypt/3rd-name")[9], $olderTime, "time unchanged by rename"); } +# Test truncate and grow sub truncate { # write to file, then truncate it @@ -95,26 +153,25 @@ sub truncate print OUT "12345"; is( -s "$crypt/trunc", 35, "truncated file size"); - seek(OUT, 0, 0); - is( Digest::MD5->new->addfile(*OUT)->hexdigest, - "5f170cc34b1944d75d86cc01496292df", "content digest"); + is( md5fh(*OUT), "5f170cc34b1944d75d86cc01496292df", + "content digest"); # try crossing block boundaries seek(OUT, 10000,0); print OUT "abcde"; - seek(OUT, 0, 0); - is( Digest::MD5->new->addfile(*OUT)->hexdigest, - "117a51c980b64dcd21df097d02206f98", "content digest"); + + is( md5fh(*OUT), "117a51c980b64dcd21df097d02206f98", + "content digest"); # then truncate back to 35 chars truncate(OUT, 35); - seek(OUT, 0, 0); - is( Digest::MD5->new->addfile(*OUT)->hexdigest, - "5f170cc34b1944d75d86cc01496292df", "content digest"); + is( md5fh(*OUT), "5f170cc34b1944d75d86cc01496292df", + "content digest"); close OUT; } +# Test file creation and removal sub fileCreation { # create a file @@ -122,8 +179,7 @@ sub fileCreation ok( -f "$crypt/df.txt", "file created" ); # ensure there is an encrypted version. - my $c = qx(./encfsctl encode --extpass="echo test" $raw df.txt); - chomp($c); + my $c = encName("df.txt"); cmp_ok( length($c), '>', 8, "encrypted name ok" ); ok( -f "$raw/$c", "encrypted file created" ); @@ -136,6 +192,46 @@ sub fileCreation ok( ! -f "$raw/$c", "file removal" ); } +# Test file growth +sub grow +{ + open(my $fh_a, "+>$crypt/grow"); + open(my $fh_b, "+>$tempDir/grow"); + + my $d = "1234567"; # Length 7 so we are not aligned to the block size + my $len = 7; + + my $old = ""; + my $errs = 0; + + my $i; + for($i=1; $i<1000; $i++) + { + print($fh_a $d); + print($fh_b $d); + + my $a = md5fh($fh_a); + my $b = md5fh($fh_b); + + my $size = $len * $i; + + # md5sums must be identical but must have changed + if($a ne $b || $a eq $old) + { + $errs++; + } + + $old = $a; + } + + ok($errs == 0, "grow file by $len bytes, $i times"); + + close($fh_a); + close($fh_b); +} + +# Helper function +# Check a file's content sub checkContents { my ($file, $expected, $testName) = @_; @@ -147,6 +243,28 @@ sub checkContents close IN; } +# Helper function +# Convert plain-text filename to encrypted filename +sub encName +{ + my $plain = shift; + my $enc = qx(./encfsctl encode --extpass="echo test" $raw $plain); + chomp($enc); + return $enc; +} + +# Helper function +# Get the MD5 sum of the file open at the filehandle +sub md5fh +{ + my $fh_orig = shift; + open(my $fh, "<&", $fh_orig); # Duplicate the file handle so the seek + seek($fh, 0, 0); # does not affect the caller + return Digest::MD5->new->addfile($fh)->hexdigest; + close($fh); +} + +# Test symlinks & hardlinks sub links { my $hardlinkTests = shift; @@ -173,6 +291,8 @@ sub links }; } +# Test mount +# Leaves the filesystem mounted - also used as a helper function sub mount { my $args = shift; @@ -190,6 +310,8 @@ sub mount ok( -f "$raw/.encfs6.xml", "created control file"); } +# Helper function +# Unmount and delete mountpoint sub cleanup { my $fusermount = qx(which fusermount); diff --git a/tests/default.xml b/tests/default.xml deleted file mode 100644 index ef399f5..0000000 --- a/tests/default.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - 20100713 - EncFS 1.7.5 - - ssl/aes - 3 - 0 - - - nameio/block - 4 - 0 - - 192 - 1024 - 1 - 1 - 0 - 0 - 0 - 1 - 44 - -AL2iRMLYyuf7RXcFCK0CcegQ/+enbaaqTq1OY3WULMLpcz0yvo8K4KtVw7c= - - 20 - -RVCTqJyxxp7ZN0AsLJhgpvSuUB8= - - 170562 - 500 - - - diff --git a/tests/mac.xml b/tests/mac.xml deleted file mode 100644 index 920d4e9..0000000 --- a/tests/mac.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - 20100713 - EncFS 1.7.5 - - ssl/aes - 3 - 0 - - - nameio/block - 4 - 0 - - 192 - 1024 - 1 - 1 - 0 - 8 - 0 - 1 - 44 - -AL2iRMLYyuf7RXcFCK0CcegQ/+enbaaqTq1OY3WULMLpcz0yvo8K4KtVw7c= - - 20 - -RVCTqJyxxp7ZN0AsLJhgpvSuUB8= - - 170562 - 500 - - - diff --git a/tests/run.sh b/tests/run.sh deleted file mode 100755 index 4af8907..0000000 --- a/tests/run.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash - -# This is the wrapper script for all tests. -# It calls the unit tests and all integration tests. -# -# Functions that are useful in more than one integration test -# should be defined here. - -set -eu - -# Set up envrionment variables, cd to the correct directory -function init { - - # Make sure we are in the "tests" directory - TESTDIR=$(dirname $(realpath "$0")) - cd $TESTDIR - - # Full path to encfs binary - ENCFS=$(realpath ../encfs/encfs) - - # Directory for temporary files (scratch) - SCRATCH=$(realpath scratch) - LOWER=$SCRATCH/lower - UPPER=$SCRATCH/upper - - # Test number counter - TESTNO=1 - - trap 'echo -e "***\e[31m test(s) FAILED\e[0m"' ERR -} - -# Run all test_*.sh files -function run_cli_tests { - - cd $TESTDIR - for i in $(echo test_*.sh) - do - cleanup - mkdir $SCRATCH - echo "*** running $i" - source $i - done -} - -# fresh_mount CONFIG -# -# Mount a fresh, empty encfs filesystem using the encfs config file -# CONFIG as .encfs6.xml. The password must be set to "a". -# The backing files will be at $LOWER, the mounted filesystem at $UPPER. -function fresh_mount { - cd $TESTDIR - fusermount -q -u $UPPER 2> /dev/null || true - wait - rm -Rf $LOWER $UPPER - mkdir -p $LOWER $UPPER - touch $UPPER/not_yet_mounted - - cp $TESTDIR/$1 $SCRATCH/lower/.encfs6.xml - - echo a | $ENCFS -f -S -o nonempty $LOWER $UPPER 2> /dev/null & - while [ -e $UPPER/not_yet_mounted ] - do - sleep 0.1s - done -} - -# Clean up scratch directory -function cleanup { - test -d $SCRATCH || return 0 - - cd $SCRATCH/.. - fusermount -q -u $UPPER 2> /dev/null || true - wait - rm -Rf $SCRATCH -} - -# Get the plain MD5 sum of a file, without the filename that is output -# by md5sum -# md5sum foo: 5f47bbbd6db883f93f5d00fd05f149ff foo -# plain_md5 foo: 5f47bbbd6db883f93f5d00fd05f149ff -function md5 { - OUTPUT=$(md5sum "$1") # Capture md5sum output - ARRAY=($OUTPUT) # Split into array - echo ${ARRAY[0]} # Return first element -} - -# Indicate beginning of a test -# Prints test number and title -function test_begin { - echo -n "$TESTNO $1: " - let TESTNO++ -} - -# Indicate successful completion of a test -function test_ok { - echo "OK" -} - -init - -test_begin "Running unit tests" -../encfs/test 2> /dev/null -test_ok - -run_cli_tests - -echo -e "***\e[32m All tests OK\e[0m" - -cleanup diff --git a/tests/test_corruption.sh b/tests/test_corruption.sh deleted file mode 100644 index 861a675..0000000 --- a/tests/test_corruption.sh +++ /dev/null @@ -1,31 +0,0 @@ -fresh_mount default.xml -cd $SCRATCH -dd if=/dev/urandom of=foo bs=1M count=1 2> /dev/null -A=$(md5 foo) -cp foo $UPPER -cd $UPPER -test_begin "Reading file" -B=$(md5 foo) -test $A = $B -test_ok - -test_begin "Reading corrupted file" -echo DEADBEEF >> $LOWER/$(ls $LOWER) -B=$(md5 foo) -test $A != $B -test_ok - -fresh_mount mac.xml -cd $SCRATCH -cp foo $UPPER -cd $UPPER -test_begin "Reading file with MAC" -B=$(md5 foo) -test $A = $B -test_ok - -test_begin "Corruption with MAC returns IO error" -echo DEADBEEF >> $LOWER/$(ls $LOWER) -md5 foo 2>&1 | grep "Input/output error" > /dev/null -test_ok - diff --git a/tests/test_file_expansion.sh b/tests/test_file_expansion.sh deleted file mode 100644 index 8abaaea..0000000 --- a/tests/test_file_expansion.sh +++ /dev/null @@ -1,40 +0,0 @@ -fresh_mount default.xml -cd $UPPER -test_begin "Creating files of different sizes: " -for i in `seq 0 50` `seq 1000 1050` -do - OUTPUT=$(dd if=/dev/zero bs=$i count=1 2> /dev/null | tee $i | md5sum) - ARRAY=($OUTPUT) - A=${ARRAY[0]} # Remove filename - B=$(md5 $i) - test $A = $B -done -test_ok - -test_begin "Growing file" -rm -f ../grow -for i in `seq 0 300` -do - echo -n "abcdefg" >> ../grow - echo -n "abcdefg" >> grow - - A=$(md5 ../grow) - B=$(md5 grow) - test "$A" = "$B" -done -test_ok - -test_begin "Internal modification" -dd if=/dev/urandom of=../internal bs=1M count=2 2> /dev/null -cp ../internal internal -for i in 0 30 1020 1200 -do - dd if=/dev/zero of=../internal bs=1 count=1 skip=$i 2> /dev/null - dd if=/dev/zero of=internal bs=1 count=1 skip=$i 2> /dev/null - - A=$(md5 ../internal) - B=$(md5 internal) - test "$A" = "$B" -done -test_ok -