=head1 NAME B - A L based module to ease testing with files and dirs. In general, the following can be tested: =over 2 =item If the contents of the file being tested match the expected pattern. =item If the file being tested is identical to the expected file in regard to contents, or size, or existence. If necessary, some parts of the contents can be excluded from the comparison. =item If the directory being tested contains all expected files. =item If the files in the directory being tested are identical to the files in the reference directory in regard to contents, or size, or existence. If necessary, some files as well as some parts of contents can be excluded from the comparison. =item If all files in the directory being tested fulfill certain requirements. =item If the archive (container) being tested is logically identical to the the reference archive (container). If necessary, some members of archives, as well as some parts of their contents, as well as some metadata can be excluded from the comparison. =back =head1 SYNOPSIS All examples listed below can be found and executed using B located on L. use Path::Tiny qw( path ); use Test::Files; my $got_file = path( 'path' )->child( qw( got file ) ); my $reference_file = path( 'path' )->child( qw( reference file ) ); my $got_dir = path( 'path' )->child( qw( got dir ) ); my $reference_dir = path( 'path' )->child( qw( reference dir with some stuff ) ); my @file_list = qw( expected file ); my ( $content_check, $expected, $filter, $options ); plan( 24 ); # Simply compares file contents to a string: $expected = "contents\nof file"; file_ok( $got_file, $expected, 'got file has expected contents' ); # Two identical variants comparing file contents # to a string ignoring differences in time stamps: $expected = "filtered contents\nof file\ncreated at 00:00:00"; $filter = sub { shift =~ s{ \b (?: [01] \d | 2 [0-3] ) : (?: [0-5] \d ) : (?: [0-5] \d ) \b } {00:00:00}grx }; $options = { FILTER => $filter }; file_ok ( $got_file, $expected, $options, "'$got_file' has contents expected after filtering" ); file_filter_ok( $got_file, $expected, $filter, "'$got_file' has contents expected after filtering" ); # Simply compares two file contents: compare_ok( $got_file, $reference_file, 'files are the same' ); # Two identical variants comparing contents of two files # ignoring differences in time stamps: $filter = sub { shift =~ s{ \b (?: [01] \d | 2 [0-3] ) : (?: [0-5] \d ) : (?: [0-5] \d ) \b } {00:00:00}grx }; $options = { FILTER => $filter }; compare_ok ( $got_file, $reference_file, $options, 'files are almost the same' ); compare_filter_ok( $got_file, $reference_file, $filter, 'files are almost the same' ); # Verifies if both got file and reference file exist: $options = { EXISTENCE_ONLY => 1 }; compare_ok( $got_file, $reference_file, $options, 'both files exist' ); # Verifies if got file and reference file have identical size: $options = { SIZE_ONLY => 1 }; compare_ok( $got_file, $reference_file, $options, 'both files have identical size' ); # Verifies if the directory has all expected files (not recursively!): $expected = [ qw( files got_dir must contain ) ]; dir_contains_ok( $got_dir, $expected, 'directory has all files in list' ); # Two identical variants doing the same verification as before, # but additionally verifying if the directory has nothing # but the expected files (not recursively!): $options = { SYMMETRIC => 1 }; dir_contains_ok ( $got_dir, $expected, $options, 'directory has exactly the files in the list' ); dir_only_contains_ok( $got_dir, $expected, 'directory has exactly the files in the list' ); # The same as before, but recursive: $options = { RECURSIVE => 1, SYMMETRIC => 1 }; dir_contains_ok( $got_dir, $expected, $options, 'directory and its subdirectories have exactly the files in the list' ); # The same as before, but ignoring files, # which names do not match the required pattern (file "must" will be skipped): $options = { NAME_PATTERN => '^[cfg]', RECURSIVE => 1, SYMMETRIC => 1 }; dir_contains_ok( $got_dir, $expected, $options, 'directory and its subdirectories ' . "have exactly the files in the list except of file 'must'" ); # Compares two directories by comparing file contents (not recursively!): compare_dirs_ok( $got_dir, $reference_dir, "all files from '$got_dir' are the same in '$reference_dir' " . '(same names, same contents), subdirs are skipped' ); # The same as before, but subdirectories are considered, too: $options = { RECURSIVE => 1 }; compare_dirs_ok( $got_dir, $reference_dir, $options, "all files from '$got_dir' and its subdirs are the same in '$reference_dir'" ); # The same as before, but only file sizes are compared: $options = { RECURSIVE => 1, SIZE_ONLY => 1 }; compare_dirs_ok( $got_dir, $reference_dir, $options, "all files from '$got_dir' and its subdirs have same sizes in '$reference_dir'" ); # The same as before, but only file existence is verified: $options = { EXISTENCE_ONLY => 1, RECURSIVE => 1 }; compare_dirs_ok( $got_dir, $reference_dir, $options, "all files from '$got_dir' and its subdirs exist in '$reference_dir'" ); # The same as before, but only files with base names starting with 'A' are considered: $options = { EXISTENCE_ONLY => 1, NAME_PATTERN => '^A', RECURSIVE => 1 }; compare_dirs_ok( $got_dir, $reference_dir, $options, "all files from '$got_dir' and its subdirs " . "with base names starting with 'A' exist in '$reference_dir'" ); # The same as before, but the symmetric verification is requested: $options = { EXISTENCE_ONLY => 1, NAME_PATTERN => '^A', RECURSIVE => 1, SYMMETRIC => 1, }; compare_dirs_ok( $got_dir, $reference_dir, $options, "all files from '$got_dir' and its subdirs with base names " . "starting with 'A' exist in '$reference_dir' and vice versa" ); # Two identical variants of comparison of two directories by file contents, # whereas these contents are first filtered # so that time stamps in form of 'HH:MM:SS' are replaced by '00:00:00' # like in examples for file_filter_ok and compare_filter_ok: $filter = sub { shift =~ s{ \b (?: [01] \d | 2 [0-3] ) : (?: [0-5] \d ) : (?: [0-5] \d ) \b } {00:00:00}grx }; $options = { FILTER => $filter }; compare_dirs_ok( $got_dir, $reference_dir, $options, "all files from '$got_dir' are the same in '$reference_dir', " . 'subdirs are skipped, differences of time stamps ignored' ); compare_dirs_filter_ok( $got_dir, $reference_dir, $filter, "all files from '$got_dir' are the same in '$reference_dir', " . 'subdirs are skipped, differences of time stamps ignored' ); # Verifies if all plain files in directory and its subdirectories # contain the word 'good' (take into consideration the -f test below # excluding special files from comparison!): $content_check = sub { my ( $file ) = @_; ! -f $file or path( $file )->slurp =~ / \b good \b /x; }; $options = { RECURSIVE => 1 }; find_ok( $got_dir, $content_check, $options, "all files from '$got_dir' and subdirectories contain the word 'good'" ); # Compares PKZIP archives considering both global and file comments. # Both archives contain the same members in different order: my $extract = sub { my ( $file ) = @_; my $zip = Archive::Zip->new(); die( "Cannot read '$file'" ) if $zip->read( $file ) != AZ_OK; die( "Cannot extract from '$file'" ) if $zip->extractTree != AZ_OK; }; my $meta_data = sub { my ( $file ) = @_; my $zip = Archive::Zip->new(); die( "Cannot read '$file'" ) if $zip->read( $file ) != AZ_OK; my %meta_data = ( '' => $zip->zipfileComment ); $meta_data{ $_->fileName } = $_->fileComment foreach $zip->members; return \%meta_data; }; my $got_compressed_content = path( "$got_file.zip" )->slurp; my $reference_compressed_content = path( "$reference_file.zip" )->slurp; ok( $got_compressed_content ne $reference_compressed_content, "'$got_file.zip' and '$reference_file.zip' are physically different, but" ); compare_archives_ok( "$got_file.zip", "$reference_file.zip", { EXTRACT => $extract, META_DATA => $meta_data }, "'$got_file.zip' and '$reference_file.zip' are logically identical" ); =head1 DESCRIPTION This module is like L or L, in fact you should use that first as shown above. It supports comparison of files and directories in different ways. Any file or directory passed to functions of this module can be both a string or an object of L. Though the test names i.e. the last parameter of every function is optional, you should provide a name of each test for a better maintainability. You should follow the lead of the L examples and use L or, if you prefer, L. This makes it much more likely that your tests will pass on a different operating system. All of the contents comparison routines provide diff diagnostic output when they report failure. The diff output style can be changed using the option B