[gparted] Add unit tests for BlockSpecial constructors and internal caching (#781978)



commit c37be28148ec6c6b2b9be4cf097f4d20513c9a14
Author: Mike Fleetwood <mike fleetwood googlemail com>
Date:   Mon May 1 20:37:16 2017 +0100

    Add unit tests for BlockSpecial constructors and internal caching (#781978)
    
    So far only includes tests of the construction of BlockSpecial objects
    and the interaction with the internal cache used to reduce the number of
    stat(2) calls.  Tests have been designed to be runable by non-root users
    and assume as little as possible about the environment by discovering
    used block device names and symbolic link name.
    
    Bug 781978 - Add Google Test C++ test framework

 .gitignore                 |    1 +
 configure.ac               |    4 +-
 tests/Makefile.am          |    6 +-
 tests/test_BlockSpecial.cc |  265 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 272 insertions(+), 4 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 86710b3..f104066 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,5 +48,6 @@ testbuild.log
 tests/*.log
 tests/*.trs
 tests/test-suite.log
+tests/test_BlockSpecial
 tests/test_dummy
 xmldocs.make
diff --git a/configure.ac b/configure.ac
index 8368c10..b5e3d24 100644
--- a/configure.ac
+++ b/configure.ac
@@ -288,9 +288,9 @@ fi
 dnl Definitions for building of and with gtest.  Gets flags for pthread via earlier
 dnl gthread package check.
 GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"
-GTEST_CXXFLAGS="$GTHREAD_CFLAGS"
+GTEST_CXXFLAGS="$GTHREAD_CFLAGS $GTKMM_CFLAGS"
 GTEST_LDFLAGS=
-GTEST_LIBS="$GTHREAD_LIBS"
+GTEST_LIBS="$GTHREAD_LIBS $GTKMM_LIBS"
 AC_SUBST([GTEST_CPPFLAGS])
 AC_SUBST([GTEST_CXXFLAGS])
 AC_SUBST([GTEST_LDFLAGS])
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dde5c69..07b0199 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -11,9 +11,11 @@ LDADD =  \
 
 # Programs to be built by "make check"
 check_PROGRAMS =  \
-       test_dummy
+       test_dummy         \
+       test_BlockSpecial
 
 # Test cases to be run by "make check"
 TESTS = $(check_PROGRAMS)
 
-test_dummy_SOURCES = test_dummy.cc
+test_dummy_SOURCES        = test_dummy.cc
+test_BlockSpecial_SOURCES = test_BlockSpecial.cc ../src/BlockSpecial.cc
diff --git a/tests/test_BlockSpecial.cc b/tests/test_BlockSpecial.cc
new file mode 100644
index 0000000..fe45a05
--- /dev/null
+++ b/tests/test_BlockSpecial.cc
@@ -0,0 +1,265 @@
+/* Copyright (C) 2017 Mike Fleetwood
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Test BlockSpecial
+ *
+ * In order to make the tests runable by non-root users in a wide
+ * variety of environments as possible the tests try to assume as small
+ * a set of available files as possible and discovers used block device
+ * names and symbolic link name.  The following files are explicitly
+ * needed for the tests:
+ *
+ *     Name                        Access    Note
+ *     -------------------------   -------   -----
+ *     /                           Stat
+ *     /proc/partitions            Read      To find any two block
+ *                                           devices
+ *     /dev/BLOCK0                 Stat      First entry from
+ *                                           /proc/partitions
+ *     /dev/BLOCK1                 stat      Second entry from
+ *                                           /proc/partitions
+ *     /dev/disk/by-path/          Readdir   To find any symlink to a
+ *                                           block device
+ *     /dev/disk/by-path/SYMLINK   Stat      First directory entry
+ *     /dev/RBLOCK                 Stat      Device to which SYMLINK
+ *                                           refers
+ *
+ * Other dummy names are pre-loaded into the internal BlockSpecial cache
+ * and never queried from the file system.
+ */
+
+#include "BlockSpecial.h"
+#include "gtest/gtest.h"
+
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glibmm/ustring.h>
+
+namespace GParted
+{
+
+// Print method for a BlockSpecial object
+std::ostream& operator<<( std::ostream & out, const BlockSpecial & bs )
+{
+       out << "BlockSpecial{\"" << bs.m_name << "\"," << bs.m_major << "," << bs.m_minor << "}";
+       return out;
+}
+
+// Helper to construct and return message for equality assertion used in:
+//     EXPECT_BSEQTUP( bs, name, major, minor )
+// Reference:
+//     Google Test, AdvancedGuide, Using a Predicate-Formatter
+::testing::AssertionResult CompareHelperBS2TUP( const char * bs_expr, const char * name_expr,
+                                                const char * major_expr, const char * minor_expr,
+                                                const BlockSpecial & bs, const Glib::ustring & name,
+                                                unsigned long major, unsigned long minor )
+{
+       if ( bs.m_name == name && bs.m_major == major && bs.m_minor == minor )
+               return ::testing::AssertionSuccess();
+       else
+               return ::testing::AssertionFailure()
+                      << "      Expected: " << bs_expr << "\n"
+                      << "      Which is: " << bs << "\n"
+                      << "To be equal to: (" << name_expr << "," << major_expr << "," << major_expr << ")";
+}
+
+// Nonfatal assertion that BlockSpecial is equal to tuple (name, major, minor)
+#define EXPECT_BSEQTUP(bs, name, major, minor)  \
+       EXPECT_PRED_FORMAT4(CompareHelperBS2TUP, bs, name, major, minor)
+
+// Return block device names numbered 0 upwards like "/dev/sda" by reading entries from
+// /proc/partitions.
+static std::string get_block_name( unsigned want )
+{
+       std::ifstream proc_partitions( "/proc/partitions" );
+       if ( ! proc_partitions )
+       {
+               ADD_FAILURE() << __func__ << "(): Failed to open '/proc/partitions'";
+               return "";
+       }
+
+       std::string line;
+       char name[100] = "";
+       unsigned entry = 0;
+       while ( getline( proc_partitions, line ) )
+       {
+               if ( sscanf( line.c_str(), "%*u %*u %*u %99s", name ) == 1 )
+               {
+                       if ( entry == want )
+                               break;
+                       entry ++;
+               }
+       }
+       proc_partitions.close();
+
+       if ( entry == want )
+               return std::string( "/dev/" ) + name;
+
+       ADD_FAILURE() << __func__ << "(): Entry " << want << " not found in '/proc/partitions'";
+       return "";
+}
+
+// Return symbolic link to a block device by reading the first entry in the directory
+// /dev/disk/by-path/.
+static std::string get_link_name()
+{
+       DIR * dir = opendir( "/dev/disk/by-path" );
+       if ( dir == NULL )
+       {
+               ADD_FAILURE() << __func__ << "(): Failed to open directory '/dev/disk/by-path'";
+               return "";
+       }
+
+       bool found = false;
+       struct dirent * dentry;
+       // Silence GCC [-Wparentheses] warning with double parentheses
+       while ( ( dentry = readdir( dir ) ) )
+       {
+               if ( strcmp( dentry->d_name, "." )  != 0 &&
+                    strcmp( dentry->d_name, ".." ) != 0    )
+               {
+                       found = true;
+                       break;
+               }
+       }
+       closedir( dir );
+
+       if ( found )
+               return std::string( "/dev/disk/by-path/" ) + dentry->d_name;
+
+       ADD_FAILURE() << __func__ << "(): No entries found in directory '/dev/disk/by-path'";
+       return "";
+}
+
+// Follow symbolic link return real path.
+static std::string follow_link_name( std::string link )
+{
+       char * rpath = realpath( link.c_str(), NULL );
+       if ( rpath == NULL )
+       {
+               ADD_FAILURE() << __func__ << "(): Failed to resolve symbolic link '" << link << "'";
+               return "";
+       }
+
+       std::string rpath_copy = rpath;
+       free( rpath );
+       return rpath_copy;
+}
+
+TEST( BlockSpecialTest, UnnamedBlockSpecialObject )
+{
+       // Test default constructor produces empty BlockSpecial object ("", 0, 0).
+       BlockSpecial::clear_cache();
+       BlockSpecial bs;
+       EXPECT_BSEQTUP( bs, "", 0, 0 );
+}
+
+TEST( BlockSpecialTest, NamedBlockSpecialObjectPlainFile )
+{
+       // Test any named plain file or directory (actually any non-block special name)
+       // produces BlockSpecial object (name, 0, 0).
+       BlockSpecial::clear_cache();
+       BlockSpecial bs( "/" );
+       EXPECT_BSEQTUP( bs, "/", 0, 0 );
+}
+
+TEST( BlockSpecialTest, NamedBlockSpecialObjectPlainFileDuplicate )
+{
+       // Test second constructed BlockSpecial object with the same named plain file or
+       // directory, where the major and minor numbers are retrieved via internal
+       // BlockSpecial cache, produce an equivalent object (name, 0, 0).
+       BlockSpecial::clear_cache();
+       BlockSpecial bs1( "/" );
+       BlockSpecial bs2( "/" );
+       EXPECT_BSEQTUP( bs1, "/", 0, 0 );
+       EXPECT_BSEQTUP( bs2, "/", 0, 0 );
+}
+
+TEST( BlockSpecialTest, NamedBlockSpecialObjectBlockDevice )
+{
+       std::string bname = get_block_name( 0 );
+
+       // Test any block special name produces BlockSpecial object (name, major, minor).
+       BlockSpecial::clear_cache();
+       BlockSpecial bs( bname );
+       EXPECT_STREQ( bs.m_name.c_str(), bname.c_str() );
+       EXPECT_TRUE( bs.m_major > 0 || bs.m_minor > 0 );
+}
+
+TEST( BlockSpecialTest, NamedBlockSpecialObjectBlockDeviceDuplicate )
+{
+       std::string bname = get_block_name( 0 );
+
+       // Test that a second object with the same name of a block device produces the
+       // same (name, major, minor).  Checking internal BlockSpecial caching again.
+       BlockSpecial::clear_cache();
+       BlockSpecial bs1( bname );
+       BlockSpecial bs2( bname );
+       EXPECT_STREQ( bs1.m_name.c_str(), bs2.m_name.c_str() );
+       EXPECT_EQ( bs1.m_major, bs2.m_major );
+       EXPECT_EQ( bs1.m_minor, bs2.m_minor );
+}
+
+TEST( BlockSpecialTest, TwoNamedBlockSpecialObjectBlockDevices )
+{
+       std::string bname1 = get_block_name( 0 );
+       std::string bname2 = get_block_name( 1 );
+
+       // Test that two different named block devices produce different
+       // (name, major, minor).
+       BlockSpecial::clear_cache();
+       BlockSpecial bs1( bname1 );
+       BlockSpecial bs2( bname2 );
+       EXPECT_STRNE( bs1.m_name.c_str(), bs2.m_name.c_str() );
+       EXPECT_TRUE( bs1.m_major != bs2.m_major || bs1.m_minor != bs2.m_minor );
+}
+
+TEST( BlockSpecialTest, NamedBlockSpecialObjectBySymlinkMatches )
+{
+       std::string lname = get_link_name();
+       std::string bname = follow_link_name( lname );
+
+       // Test that a symbolic link to a block device and the named block device produce
+       // BlockSpecial objects with different names but the same major, minor pair.
+       BlockSpecial::clear_cache();
+       BlockSpecial lnk( lname );
+       BlockSpecial bs( bname );
+       EXPECT_STRNE( lnk.m_name.c_str(), bs.m_name.c_str() );
+       EXPECT_EQ( lnk.m_major, bs.m_major );
+       EXPECT_EQ( lnk.m_minor, bs.m_minor );
+}
+
+TEST( BlockSpecialTest, PreRegisteredCacheUsedBeforeFileSystem )
+{
+       // Test that the cache is used before querying the file system by registering a
+       // deliberately wrong major, minor pair for /dev/null and ensuring those are the
+       // major, minor numbers reported for the device.
+       BlockSpecial::clear_cache();
+       BlockSpecial::register_block_special( "/dev/null", 4, 8 );
+       BlockSpecial bs( "/dev/null" );
+       EXPECT_BSEQTUP( bs, "/dev/null", 4, 8 );
+}
+
+// FIXME: Write tests to fully check operator==() and operator<() are working as intended.
+
+}  // namespace GParted


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]