aboutsummaryrefslogtreecommitdiff
path: root/tests/path-entry
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2020-03-07 14:07:28 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2020-03-09 14:18:20 +0300
commitdcccba655fe848564e961b3f285ce3a82d3ac73a (patch)
tree598ced3b406d80c23798672930e1a17cfe112b75 /tests/path-entry
parent63b2988e4f2630cc688ff43b7e5f0d4f977896cd (diff)
Add more support for symlinks on Windows
See mksymlink() for details of the symlinks support on Windows.
Diffstat (limited to 'tests/path-entry')
-rw-r--r--tests/path-entry/driver.cxx212
-rw-r--r--tests/path-entry/testscript175
2 files changed, 353 insertions, 34 deletions
diff --git a/tests/path-entry/driver.cxx b/tests/path-entry/driver.cxx
index d48bf49..51ac04d 100644
--- a/tests/path-entry/driver.cxx
+++ b/tests/path-entry/driver.cxx
@@ -4,7 +4,9 @@
#include <cassert>
#ifndef __cpp_lib_modules_ts
+#include <string>
#include <iostream>
+#include <stdexcept> // invalid_argument
#include <system_error>
#endif
@@ -15,48 +17,210 @@
import std.core;
import std.io;
#endif
+import butl.path;
import butl.utility; // operator<<(ostream, exception)
+import butl.optional;
+import butl.timestamp;
import butl.filesystem;
#else
+#include <libbutl/path.mxx>
#include <libbutl/utility.mxx>
+#include <libbutl/optional.mxx>
+#include <libbutl/timestamp.mxx>
#include <libbutl/filesystem.mxx>
#endif
using namespace std;
using namespace butl;
-// Usage: argv[0] <path>
+// Usage: argv[0] [-l] [-t] [-p <permissions>] [-m <time>] [-a <time>] <path>
//
-// If path entry exists then print it's type and size (meaningful for the
-// regular file only) to STDOUT, and exit with the zero code. Otherwise exit
-// with the one code. Don't follow symlink. On failure print the error
-// description to STDERR and exit with the two code.
+// If path entry exists then optionally modify its meta-information and print
+// its type, size (meaningful for the regular file only), permissions,
+// modification and access times to STDOUT, one value per line, and exit with
+// the zero code. Otherwise exit with the one code. Don't follow symlink by
+// default. On failure print the error description to STDERR and exit with
+// the two code.
+//
+// -l
+// Follow symlinks.
+//
+// -t
+// Assume the path is a file and touch it. Implies -l.
+//
+// -p <permissions>
+// Set path permissions specified in the chmod utility octal form. Implies
+// -l.
+//
+// -m <time>
+// Set path modification time specified in the "%Y-%m-%d %H:%M:%S%[.N]"
+// format. Implies -l.
+//
+// -a <time>
+// As -m but set the access time.
//
int
main (int argc, const char* argv[])
-try
{
- assert (argc == 2);
+ string stage;
+
+ try
+ {
+ using butl::optional;
+
+ bool follow_symlinks (false);
+ optional<permissions> perms;
+ optional<timestamp> mtime;
+ optional<timestamp> atime;
+ bool touch (false);
+
+ auto time = [] (const char* v)
+ {
+ return from_string (v, "%Y-%m-%d %H:%M:%S%[.N]", true /* local */);
+ };
+
+ int i (1);
+ for (; i != argc; ++i)
+ {
+ string v (argv[i]);
+
+ if (v == "-l")
+ follow_symlinks = true;
+ else if (v == "-t")
+ {
+ touch = true;
+ follow_symlinks = true;
+ }
+ else if (v == "-p")
+ {
+ assert (++i != argc);
+ v = argv[i];
+
+ size_t n;
+ perms = static_cast<permissions> (stoull (v, &n, 8));
+ assert (n == v.size ());
+
+ follow_symlinks = true;
+ }
+ else if (v == "-m")
+ {
+ assert (++i != argc);
+ mtime = time (argv[i]);
+
+ follow_symlinks = true;
+ }
+ else if (v == "-a")
+ {
+ assert (++i != argc);
+ atime = time (argv[i]);
+
+ follow_symlinks = true;
+ }
+ else
+ break;
+ }
+
+ assert (i == argc - 1);
+
+ path p (argv[i]);
+
+ if (touch)
+ {
+ stage = "touch";
+ touch_file (p);
+ }
+
+ stage = "stat entry";
+ pair<bool, entry_stat> es (path_entry (p, follow_symlinks));
- auto es (path_entry (argv[1]));
+ if (!es.first)
+ return 1;
- if (!es.first)
- return 1;
+ // The entry is a directory with a symlink followed.
+ //
+ bool tdir;
- switch (es.second.type)
+ if (follow_symlinks || es.second.type != entry_type::symlink)
+ tdir = (es.second.type == entry_type::directory);
+ else
+ {
+ stage = "stat target";
+ pair<bool, entry_stat> ts (path_entry (p, true /* follow_symlinks */));
+
+ if (!ts.first)
+ return 1;
+
+ tdir = (ts.second.type == entry_type::directory);
+ }
+
+ if (perms)
+ {
+ stage = "set permissions";
+ path_permissions (p, *perms);
+ }
+
+ if (mtime)
+ {
+ if (tdir)
+ {
+ stage = "set directory mtime";
+ dir_mtime (path_cast<dir_path> (p), *mtime);
+ }
+ else
+ {
+ stage = "set file mtime";
+ file_mtime (p, *mtime);
+ }
+ }
+
+ if (atime)
+ {
+ if (tdir)
+ {
+ stage = "set directory atime";
+ dir_atime (path_cast<dir_path> (p), *atime);
+ }
+ else
+ {
+ stage = "set file atime";
+ file_atime (p, *atime);
+ }
+ }
+
+ cout << "type: ";
+
+ switch (es.second.type)
+ {
+ case entry_type::unknown: cout << "unknown"; break;
+ case entry_type::regular: cout << "regular"; break;
+ case entry_type::directory: cout << "directory"; break;
+ case entry_type::symlink: cout << "symlink"; break;
+ case entry_type::other: cout << "other"; break;
+ }
+
+ stage = "get permissions";
+
+ cout << endl
+ << "size: " << es.second.size << endl
+ << "permissions: " << oct
+ << static_cast<size_t> (path_permissions (p)) << endl;
+
+ stage = tdir ? "get directory times" : "get file times";
+
+ entry_time et (tdir ? dir_time (path_cast<dir_path> (p)) : file_time (p));
+ cout << "mtime: " << et.modification << endl
+ << "atime: " << et.access << endl;
+
+ return 0;
+ }
+ catch (const invalid_argument& e)
{
- case entry_type::unknown: cout << "unknown"; break;
- case entry_type::regular: cout << "regular"; break;
- case entry_type::directory: cout << "directory"; break;
- case entry_type::symlink: cout << "symlink"; break;
- case entry_type::other: cout << "other"; break;
+ cerr << e << endl;
+ return 2;
+ }
+ catch (const system_error& e)
+ {
+ cerr << stage << " failed: " << e << endl;
+ return 2;
}
-
- cout << endl << es.second.size << endl;
- return 0;
-}
-catch (const system_error& e)
-{
- cerr << e << endl;
- return 2;
}
diff --git a/tests/path-entry/testscript b/tests/path-entry/testscript
index 456f96f..76316bf 100644
--- a/tests/path-entry/testscript
+++ b/tests/path-entry/testscript
@@ -10,21 +10,176 @@
: printed on Windows. This why we exclude it, to get consistent behavior on
: both POSIX and Windows.
:
- cat <:'abc' >=f;
- $* f >>EOO
- regular
- 3
- EOO
+ {
+ cat <:'abc' >=f;
+ $* f >>~/EOO/
+ type: regular
+ size: 3
+ /.+
+ EOO
+ }
: dir
:
: Note that the size value is meaningless for directory entries.
:
- mkdir -p d;
- $* d >>~/EOO/
- directory
- /.
- EOO
+ {
+ mkdir -p d;
+ $* d >>~/EOO/
+ type: directory
+ /.+
+ EOO
+ }
+
+ : followed-symlink
+ :
+ {
+ cat <:'abc' >=f;
+ ln -s f l;
+ $* -l l >>~/EOO/
+ type: regular
+ size: 3
+ /.+
+ EOO
+ }
+
+ : symlink
+ :
+ : If we are not cross-testing let's test if symlinks are properly followed.
+ : On Windows that involves mklink command usability test. If we fail to
+ : create a trial link (say because we are not in the Developer Mode and are
+ : running non-elevated console), then the test group will be silently
+ : skipped.
+ :
+ if ($test.target == $build.host)
+ {
+ +if ($cxx.target.class != 'windows')
+ lns = ln -s t l &l
+ else
+ echo 'yes' >=t
+ if cmd /C 'mklink l t' >- 2>- &?l && cat l >'yes'
+ lns = cmd /C 'mklink l t' &l >-
+ end
+
+ jnc = cmd /C 'mklink /J l t' &l >-
+ end
+
+ : symlink
+ :
+ if! $empty($lns)
+ {
+ : file
+ :
+ {
+ : get-info
+ :
+ : Test that the target type, size, permissions and file times are
+ : obtained via a symlink.
+ :
+ {
+ cat <:'abc' >=t;
+ $lns;
+ $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' t | set ti;
+ $* -l l >"$ti"
+ }
+
+ : set-info
+ :
+ : Test that permissions and file times are set via a symlink.
+ :
+ {
+ cat <:'abc' >=t;
+ $lns;
+ $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' l | set ti;
+ sed -n -e 's/permissions: (.+)/\1/p' <"$ti" >~/'4.{2}'/;
+ sed -n -e 's/mtime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:00';
+ sed -n -e 's/atime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:01'
+ }
+
+ : touch
+ :
+ : Test that a symlink touch touches the target.
+ :
+ {
+ cat <:'abc' >=t;
+ $lns;
+ $* t | set ti;
+ sleep 2;
+ $* -t l | set li;
+ if ("$ti" == "$li")
+ exit "link touch doesn't change target"
+ end
+ }
+ }
+
+ : dir
+ :
+ : Note that the following tests may fail on Windows (see symlinks known
+ : issues in libbutl/filesystem.mxx).
+ :
+ if ($cxx.target.class != 'windows')
+ {
+ : get-info
+ :
+ : Test that the target type, size, permissions and file times are
+ : obtained via a symlink.
+ :
+ {
+ mkdir t;
+ $lns;
+ $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' t | set ti;
+ $* -l l >"$ti";
+ $* -p 600 t >- # @@ TMP; until build2 is staged.
+ }
+
+ : set-info
+ :
+ : Test that permissions and file times are set via a symlink.
+ :
+ {
+ mkdir t;
+ $lns;
+ $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' l | set ti;
+ sed -n -e 's/permissions: (.+)/\1/p' <"$ti" >~/'4.{2}'/;
+ sed -n -e 's/mtime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:00';
+ sed -n -e 's/atime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:01';
+ $* -p 600 t >- # @@ TMP; until build2 is staged.
+ }
+ }
+ }
+
+ : junction
+ :
+ if! $empty($jnc)
+ {
+ : get-info
+ :
+ : Test that the target type, size, permissions and file times are
+ : obtained via a junction.
+ :
+ {
+ mkdir t;
+ $jnc;
+ $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' t | set ti;
+ $* -l l >"$ti";
+ $* -p 600 t >- # @@ TMP; until build2 is staged.
+ }
+
+ : set-info
+ :
+ : Test that permissions and file times are set via a junction.
+ :
+ {
+ mkdir t;
+ $jnc;
+ $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' l | set ti;
+ sed -n -e 's/permissions: (.+)/\1/p' <"$ti" >~/'4.{2}'/;
+ sed -n -e 's/mtime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:00';
+ sed -n -e 's/atime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:01';
+ $* -p 600 t >- # @@ TMP; until build2 is staged.
+ }
+ }
+ }
}
: non-existent