[gcc r9-8366] libstdc++: Fix FS-dependent filesystem tests

Jonathan Wakely redi@gcc.gnu.org
Thu Mar 12 17:39:39 GMT 2020


https://gcc.gnu.org/g:2fa3247fef79ede9ec3638605ea137b0e4d76075

commit r9-8366-g2fa3247fef79ede9ec3638605ea137b0e4d76075
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Mar 12 17:39:04 2020 +0000

    libstdc++: Fix FS-dependent filesystem tests
    
    These tests were failing on XFS because it doesn't support setting file
    timestamps past 2038, so the expected overflow when reading back a huge
    timestamp into a file_time_type didn't happen.
    
    Additionally, the std::filesystem::file_time_type::clock has an
    epoch that is out of range of 32-bit time_t so testing times around that
    epoch may also fail.
    
    This fixes the tests to give up gracefully if the filesystem doesn't
    support times that can't be represented in 32-bit time_t.
    
    Backport from mainline
    2020-02-28  Jonathan Wakely  <jwakely@redhat.com>
    
            * testsuite/27_io/filesystem/operations/last_write_time.cc: Fixes for
            filesystems that silently truncate timestamps.
            * testsuite/experimental/filesystem/operations/last_write_time.cc:
            Likewise.

Diff:
---
 libstdc++-v3/ChangeLog                             |  8 +++
 .../27_io/filesystem/operations/last_write_time.cc | 77 ++++++++++++++++------
 .../filesystem/operations/last_write_time.cc       | 58 +++++++++++-----
 3 files changed, 107 insertions(+), 36 deletions(-)

diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog
index 000ecbfbad1..7608f7028f6 100644
--- a/libstdc++-v3/ChangeLog
+++ b/libstdc++-v3/ChangeLog
@@ -1,5 +1,13 @@
 2020-03-12  Jonathan Wakely  <jwakely@redhat.com>
 
+	Backport from mainline
+	2020-02-28  Jonathan Wakely  <jwakely@redhat.com>
+
+	* testsuite/27_io/filesystem/operations/last_write_time.cc: Fixes for
+	filesystems that silently truncate timestamps.
+	* testsuite/experimental/filesystem/operations/last_write_time.cc:
+	Likewise.
+
 	Backport from mainline
 	2020-01-13  Jonathan Wakely  <jwakely@redhat.com>
 
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc b/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc
index 3f31375f51b..5d6e62c421a 100644
--- a/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc
+++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc
@@ -32,9 +32,12 @@
 #if _GLIBCXX_HAVE_UTIME_H
 # include <utime.h>
 #endif
+#include <stdio.h>
 
 using time_type = std::filesystem::file_time_type;
 
+namespace chrono = std::chrono;
+
 void
 test01()
 {
@@ -67,10 +70,15 @@ test01()
 
   auto end_of_time = time_type::duration::max();
   auto last_second
-    = std::chrono::duration_cast<std::chrono::seconds>(end_of_time).count();
+    = chrono::duration_cast<chrono::seconds>(end_of_time).count();
   if (last_second > std::numeric_limits<std::time_t>::max())
-    return; // can't test overflow
+  {
+    puts("Range of time_t is smaller than range of chrono::file_clock, "
+	 "can't test for overflow on this target.");
+    return;
+  }
 
+  // Set mtime to a date past the maximum possible file_time_type:
 #if _GLIBCXX_USE_UTIMENSAT
   struct ::timespec ts[2];
   ts[0].tv_sec = 0;
@@ -84,25 +92,34 @@ test01()
   times.actime = std::numeric_limits<std::time_t>::max() - 1;
   VERIFY( !::utime(p.string().c_str(), &times) );
 #else
+  puts("No utimensat or utime, giving up.");
   return;
 #endif
 
+  // Try to read back the impossibly-large mtime:
   mtime = last_write_time(p, ec);
-  VERIFY( ec );
-  VERIFY( ec == std::make_error_code(std::errc::value_too_large) );
-  VERIFY( mtime == time_type::min() );
+  // Some filesystems (e.g. XFS) silently truncate distant times to
+  // the time_t epochalypse, Jan 19 2038, so we won't get an error when
+  // reading it back:
+  if (ec)
+  {
+    VERIFY( ec == std::make_error_code(std::errc::value_too_large) );
+    VERIFY( mtime == time_type::min() );
+  }
+  else
+    puts("No overflow error, filesystem may not support 64-bit time_t.");
 
 #if __cpp_exceptions
-  caught = false;
+  // Once more, with exceptions:
   try {
-    mtime = last_write_time(p);
-  } catch (std::system_error const& e) {
-    caught = true;
-    ec = e.code();
+    auto mtime2 = last_write_time(p);
+    // If it didn't throw, expect to have read back the same value:
+    VERIFY( mtime2 == mtime );
+  } catch (std::filesystem::filesystem_error const& e) {
+    // If it did throw, expect the error_code to be the same:
+    VERIFY( e.code() == ec );
+    VERIFY( e.path1() == p );
   }
-  VERIFY( caught );
-  VERIFY( ec );
-  VERIFY( ec == std::make_error_code(std::errc::value_too_large) );
 #endif
 }
 
@@ -111,7 +128,7 @@ bool approx_equal(time_type file_time, time_type expected)
   auto delta = expected - file_time;
   if (delta < delta.zero())
     delta = -delta;
-  return delta < std::chrono::seconds(1);
+  return delta < chrono::seconds(1);
 }
 
 void
@@ -124,20 +141,20 @@ test02()
   std::error_code ec;
   time_type time;
 
-  time = last_write_time(f.path);
   ec = bad_ec;
+  time = last_write_time(f.path);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
 
   ec = bad_ec;
-  time -= std::chrono::milliseconds(1000 * 60 * 10 + 15);
+  time -= chrono::milliseconds(1000 * 60 * 10 + 15);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
 
   ec = bad_ec;
-  time += std::chrono::milliseconds(1000 * 60 * 20 + 15);
+  time += chrono::milliseconds(1000 * 60 * 20 + 15);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
@@ -146,6 +163,28 @@ test02()
       < std::numeric_limits<std::int64_t>::max())
     return; // file clock's epoch is out of range for 32-bit time_t
 
+  using sys_time_32b
+    = chrono::time_point<chrono::system_clock, chrono::duration<std::int32_t>>;
+  auto duration_until_2038 = sys_time_32b::max() - sys_time_32b::clock::now();
+  auto file_time_2038 = time_type::clock::now() + duration_until_2038;
+
+  ec = bad_ec;
+  time = file_time_2038 - chrono::seconds(1);
+  // Assume all filesystems can store times that fit in 32-bit time_t
+  // (i.e. up to Jan 19 2038)
+  last_write_time(f.path, time, ec);
+  VERIFY( !ec );
+  VERIFY( approx_equal(last_write_time(f.path), time) );
+
+  // Check whether the filesystem supports times larger than 32-bit time_t:
+  time += chrono::seconds(60);
+  last_write_time(f.path, time, ec);
+  if (ec || !approx_equal(last_write_time(f.path), time))
+  {
+    puts("Filesystem seems to truncate times past Jan 19 2038, giving up.");
+    return; // Tests below will fail on this filesystem
+  }
+
   ec = bad_ec;
   // The file clock's epoch:
   time = time_type();
@@ -155,14 +194,14 @@ test02()
 
   ec = bad_ec;
   // A time after the epoch
-  time += std::chrono::milliseconds(1000 * 60 * 10 + 15);
+  time += chrono::milliseconds(1000 * 60 * 10 + 15);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
 
   ec = bad_ec;
   // A time before than the epoch
-  time -= std::chrono::milliseconds(1000 * 60 * 20 + 15);
+  time -= chrono::milliseconds(1000 * 60 * 20 + 15);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc
index 1bbc37408c3..82061f9e7e9 100644
--- a/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc
+++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc
@@ -22,6 +22,7 @@
 // 15.25 Permissions [fs.op.last_write_time]
 
 #include <experimental/filesystem>
+#include <limits>
 #include <testsuite_fs.h>
 #include <testsuite_hooks.h>
 
@@ -31,9 +32,12 @@
 #if _GLIBCXX_HAVE_UTIME_H
 # include <utime.h>
 #endif
+#include <stdio.h>
 
 using time_type = std::experimental::filesystem::file_time_type;
 
+namespace chrono = std::chrono;
+
 void
 test01()
 {
@@ -66,10 +70,15 @@ test01()
 
   auto end_of_time = time_type::duration::max();
   auto last_second
-    = std::chrono::duration_cast<std::chrono::seconds>(end_of_time).count();
+    = chrono::duration_cast<chrono::seconds>(end_of_time).count();
   if (last_second > std::numeric_limits<std::time_t>::max())
-    return; // can't test overflow
+  {
+    puts("Range of time_t is smaller than range of chrono::file_clock, "
+	 "can't test for overflow on this target.");
+    return;
+  }
 
+  // Set mtime to a date past the maximum possible file_time_type:
 #if _GLIBCXX_USE_UTIMENSAT
   struct ::timespec ts[2];
   ts[0].tv_sec = 0;
@@ -83,25 +92,34 @@ test01()
   times.actime = std::numeric_limits<std::time_t>::max() - 1;
   VERIFY( !::utime(p.string().c_str(), &times) );
 #else
+  puts("No utimensat or utime, giving up.");
   return;
 #endif
 
+  // Try to read back the impossibly-large mtime:
   mtime = last_write_time(p, ec);
-  VERIFY( ec );
-  VERIFY( ec == std::make_error_code(std::errc::value_too_large) );
-  VERIFY( mtime == time_type::min() );
+  // Some filesystems (e.g. XFS) silently truncate distant times to
+  // the time_t epochalypse, Jan 19 2038, so we won't get an error when
+  // reading it back:
+  if (ec)
+  {
+    VERIFY( ec == std::make_error_code(std::errc::value_too_large) );
+    VERIFY( mtime == time_type::min() );
+  }
+  else
+    puts("No overflow error, filesystem may not support 64-bit time_t.");
 
 #if __cpp_exceptions
-  caught = false;
+  // Once more, with exceptions:
   try {
-    mtime = last_write_time(p);
-  } catch (std::system_error const& e) {
-    caught = true;
-    ec = e.code();
+    auto mtime2 = last_write_time(p);
+    // If it didn't throw, expect to have read back the same value:
+    VERIFY( mtime2 == mtime );
+  } catch (std::experimental::filesystem::filesystem_error const& e) {
+    // If it did throw, expect the error_code to be the same:
+    VERIFY( e.code() == ec );
+    VERIFY( e.path1() == p );
   }
-  VERIFY( caught );
-  VERIFY( ec );
-  VERIFY( ec == std::make_error_code(std::errc::value_too_large) );
 #endif
 }
 
@@ -110,7 +128,7 @@ bool approx_equal(time_type file_time, time_type expected)
   auto delta = expected - file_time;
   if (delta < delta.zero())
     delta = -delta;
-  return delta < std::chrono::seconds(1);
+  return delta < chrono::seconds(1);
 }
 
 void
@@ -118,31 +136,37 @@ test02()
 {
   // write times
 
+  const std::error_code bad_ec = make_error_code(std::errc::invalid_argument);
   __gnu_test::scoped_file f;
   std::error_code ec;
   time_type time;
 
+  ec = bad_ec;
   time = last_write_time(f.path);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
 
-  time -= std::chrono::milliseconds(1000 * 60 * 10 + 15);
+  ec = bad_ec;
+  time -= chrono::milliseconds(1000 * 60 * 10 + 15);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
 
-  time += std::chrono::milliseconds(1000 * 60 * 20 + 15);
+  ec = bad_ec;
+  time += chrono::milliseconds(1000 * 60 * 20 + 15);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
 
+  ec = bad_ec;
   time = time_type();
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );
 
-  time -= std::chrono::milliseconds(1000 * 60 * 10 + 15);
+  ec = bad_ec;
+  time -= chrono::milliseconds(1000 * 60 * 10 + 15);
   last_write_time(f.path, time, ec);
   VERIFY( !ec );
   VERIFY( approx_equal(last_write_time(f.path), time) );


More information about the Libstdc++-cvs mailing list