summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2020-05-23 15:04:54 +0100
committerEdward Thomson <ethomson@edwardthomson.com>2020-06-02 09:03:48 +0100
commit8df4f51956ba711ca69e93b8c93366ba7eaaab0b (patch)
treea73ecae0f5cd677a5933d6dd51fecc5ff979c06a
parent849f371ea89257512ef505d0094d77b1ec812c53 (diff)
downloadlibgit2-8df4f51956ba711ca69e93b8c93366ba7eaaab0b.tar.gz
clar: copy files internally instead of /bin/cp
clar has historically shelled out to `/bin/cp` to copy test fixtures into a sandbox. This has two deficiencies: 1. It's slower than simply opening the source and destination and copying them in a read/write loop. On my Mac, the `/bin/cp` based approach takes ~2:40 for a full test pass. Using a read/write loop to copy the files ourselves takes ~1:50. 2. It's noisy. Since the leak detector follows fork/exec, we'll end up running the leak detector on `/bin/cp`. This would be fine, except that the leak detector spams the console on startup and shutdown, so it adds a _lot_ of additional information to the test runs that is useless. By not forking and using this internal system, we see much less output.
-rw-r--r--tests/clar/fs.h161
1 files changed, 143 insertions, 18 deletions
diff --git a/tests/clar/fs.h b/tests/clar/fs.h
index 7c7dde6fc..696874270 100644
--- a/tests/clar/fs.h
+++ b/tests/clar/fs.h
@@ -1,3 +1,6 @@
+/* fcopyfile on macOS is slower than a simple read/write loop? */
+#undef USE_FCOPYFILE
+
#ifdef _WIN32
#define RM_RETRY_COUNT 5
@@ -254,6 +257,16 @@ cl_fs_cleanup(void)
#include <errno.h>
#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__APPLE__) || defined(__FreeBSD__)
+# include <copyfile.h>
+#endif
static int
shell_out(char * const argv[])
@@ -281,31 +294,143 @@ shell_out(char * const argv[])
return WEXITSTATUS(status);
}
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
static void
-fs_copy(const char *_source, const char *dest)
+fs_copydir_helper(const char *source, const char *dest, int dmode)
{
- char *argv[5];
- char *source;
- size_t source_len;
+ DIR *source_dir;
+ struct dirent *d;
- source = strdup(_source);
- source_len = strlen(source);
+ mkdir(dest, dmode);
- if (source[source_len - 1] == '/')
- source[source_len - 1] = 0;
+ cl_assert_(source_dir = opendir(source), "Could not open source dir");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
- argv[0] = "/bin/cp";
- argv[1] = "-R";
- argv[2] = source;
- argv[3] = (char *)dest;
- argv[4] = NULL;
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
- cl_must_pass_(
- shell_out(argv),
- "Failed to copy test fixtures to sandbox"
- );
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, const char *dest, int dmode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dmode)));
+
+#if USE_FCOPYFILE && (defined(__APPLE__) || defined(__FreeBSD__))
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, dest, source_st.st_mode);
+ }
- free(source);
+ free(dbuf);
}
static void