libstdc++: Fix FS-dependent filesystem tests

Message ID 20200228132522.GA2605829@redhat.com
State New
Headers show
Series
  • libstdc++: Fix FS-dependent filesystem tests
Related show

Commit Message

Jonathan Wakely Feb. 28, 2020, 1:25 p.m.
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.

	* 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.

Tested powerpc64le-linux and x86_64-linux, on ext4 and XFS.
commit a51a546c1704cd572c35c11e539568c04d99e7d1
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Feb 27 16:38:00 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.
    
            * 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.

Comments

Jonathan Wakely Feb. 28, 2020, 1:38 p.m. | #1
On 28/02/20 13:25 +0000, Jonathan Wakely wrote:
>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.

>

>	* 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.

>

>Tested powerpc64le-linux and x86_64-linux, on ext4 and XFS.


Huh. I've just noticed that the powerpc64le machine (gcc112) also has
XFS, but on that system I can set (and read back) times past the
epochalypse. Here's a file on that host:

-rw-rw-r--. 1 jwakely jwakely 0 9223372036854775806 filesystem-test.bPba4m-last_write_time

Which matches what I get on my machine's tmpfs partition:

-rw-rw-r--. 1 jwakely jwakely 0 9223372036854775806 /tmp/filesystem-test.R6DKwt-last_write_time

But on my machine's XFS partition the same timestamp gets silently
truncated:

-rw-rw-r--. 1 jwakely jwakely 0 Jan 19  2038 testsuite/filesystem-test.r8FeLP-last_write_time

I don't know how the XFS disk on gcc112 can store 64-bit time stamps
when XFS doesn't support them. Odd.

Anyway, the patch is committed and I no longer see failures on my XFS
disks.

Patch

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 a6be926143c..2bba02f6899 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;
 
+  ec = bad_ec;
   time = last_write_time(f.path);
-  ec = bad_ec;
   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 4e3ea6754f9..13313a9a640 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) );