authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-03-30 23:41:13+01:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-09 15:00:21+02:00
log373d48212f91ab1fd345da49f8a3c737075274d4
tree1914b944c49b8b58a11484000babf0b0aaafd235
parent5a38924a7d41fee0ac5544d9ddb85ce82bc92030

fetch: add pathological packages test

Using test cases from: https://github.com/ianprime0509/pathological-packages repository. Depends on existence of the FAT32 file system. Folder is in FAT32 file system because it is case insensitive and and does not support symlinks. It is complicated test case requires internet connection, depends on existence of FAT32 in the specific location. But it is so valuable for development. Running `zig test Package.zig` is so much faster than building zig binary and running `zig fetch URL`. Committing it here although it should probably be removed.

1 files changed, 131 insertions(+), 10 deletions(-)

src/Package/Fetch.zig+131-10
......@@ -1890,16 +1890,19 @@ const UnpackResult = struct {
18901890 }
18911891};
18921892
1893test "fetch tarball: fail with unable to create file" {
1893test "tarball with duplicate file names" {
1894 const gpa = std.testing.allocator;
18941895 var tmp = std.testing.tmpDir(.{});
18951896 defer tmp.cleanup();
18961897
18971898 const tarball_name = "package.tar";
18981899 try createTestTarball(tmp.dir, tarball_name, false);
1900 const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
1901 defer gpa.free(tarball_path);
18991902
19001903 // Run tarball fetch, expect to fail
19011904 var fb: TestFetchBuilder = undefined;
1902 var fetch = try fb.build(std.testing.allocator, tmp, tarball_name);
1905 var fetch = try fb.build(gpa, tmp.dir, tarball_path);
19031906 defer fb.deinit();
19041907 try std.testing.expectError(error.FetchFailed, fetch.run());
19051908
......@@ -1911,16 +1914,19 @@ test "fetch tarball: fail with unable to create file" {
19111914 );
19121915}
19131916
1914test "fetch tarball: error path are excluded" {
1917test "tarball with error paths excluded" {
1918 const gpa = std.testing.allocator;
19151919 var tmp = std.testing.tmpDir(.{});
19161920 defer tmp.cleanup();
19171921
19181922 const tarball_name = "package.tar";
19191923 try createTestTarball(tmp.dir, tarball_name, true);
1924 const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
1925 defer gpa.free(tarball_path);
19201926
19211927 // Run tarball fetch, should succeed
19221928 var fb: TestFetchBuilder = undefined;
1923 var fetch = try fb.build(std.testing.allocator, tmp, tarball_name);
1929 var fetch = try fb.build(std.testing.allocator, tmp.dir, tarball_path);
19241930 defer fb.deinit();
19251931 try fetch.run();
19261932
......@@ -1944,9 +1950,8 @@ const TestFetchBuilder = struct {
19441950 job_queue: Fetch.JobQueue,
19451951 fetch: Fetch,
19461952
1947 fn build(self: *TestFetchBuilder, allocator: std.mem.Allocator, tmp: std.testing.TmpDir, tarball_name: []const u8) !*Fetch {
1948 const cache_dir = try tmp.dir.makeOpenPath("zig-global-cache", .{});
1949 const path_or_url = try std.fmt.allocPrint(allocator, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
1953 fn build(self: *TestFetchBuilder, allocator: std.mem.Allocator, cache_parent_dir: std.fs.Dir, path_or_url: []const u8) !*Fetch {
1954 const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{});
19501955
19511956 try self.thread_pool.init(.{ .allocator = allocator });
19521957 self.http_client = .{ .allocator = allocator };
......@@ -1993,7 +1998,6 @@ const TestFetchBuilder = struct {
19931998 }
19941999
19952000 fn deinit(self: *TestFetchBuilder) void {
1996 self.fetch.arena.child_allocator.free(self.fetch.location.path_or_url);
19972001 self.fetch.deinit();
19982002 self.job_queue.deinit();
19992003 self.root_prog_node.end();
......@@ -2043,8 +2047,9 @@ const TestFetchBuilder = struct {
20432047
20442048 const em = errors.getErrorMessage(errors.getMessages()[0]);
20452049 try std.testing.expectEqual(1, em.count);
2046 try std.testing.expectEqual(notes_len, em.notes_len);
2047
2050 if (notes_len > 0) {
2051 try std.testing.expectEqual(notes_len, em.notes_len);
2052 }
20482053 var al = std.ArrayList(u8).init(std.testing.allocator);
20492054 defer al.deinit();
20502055 try errors.renderToWriter(.{ .ttyconf = .no_color }, al.writer());
......@@ -2098,3 +2103,119 @@ fn createTestTarball(dir: fs.Dir, tarball_name: []const u8, with_manifest: bool)
20982103 try file.writeAll(&[_]u8{0} ** (512 - build_zig_zon.len));
20992104 }
21002105}
2106
2107// Using test cases from: https://github.com/ianprime0509/pathological-packages
2108// repository. Depends on existence of the FAT32 file system at /tmp/fat32.mnt
2109// (look at the fat32TmpDir function below how to create it). If that folder is
2110// not found test will be skipped. Folder is in FAT32 file system because it is
2111// case insensitive and and does not support symlinks.
2112test "pathological packages" {
2113 const gpa = std.testing.allocator;
2114 var buf: [128]u8 = undefined;
2115
2116 const urls: []const []const u8 = &.{
2117 "https://github.com/ianprime0509/pathological-packages/archive/{s}.tar.gz",
2118 "git+https://github.com/ianprime0509/pathological-packages#{s}",
2119 };
2120 const branches: []const []const u8 = &.{
2121 "excluded-case-collisions",
2122 "excluded-symlinks",
2123 "included-case-collisions",
2124 "included-symlinks",
2125 };
2126
2127 // Expected fetched package files or error message for each combination of url/branch.
2128 const expected = [_]struct {
2129 files: []const []const u8 = &.{},
2130 err_msg: []const u8 = "",
2131 }{
2132 // tar
2133 .{ .files = &.{ "build.zig", "build.zig.zon" } },
2134 .{ .files = &.{ "build.zig", "build.zig.zon", "main" } },
2135 .{ .err_msg =
2136 \\error: unable to unpack tarball
2137 \\ note: unable to create file 'main': PathAlreadyExists
2138 \\ note: unable to create file 'subdir/main': PathAlreadyExists
2139 \\
2140 },
2141 .{ .err_msg =
2142 \\error: unable to unpack tarball
2143 \\ note: unable to create symlink from 'link' to 'main': AccessDenied
2144 \\ note: unable to create symlink from 'subdir/link' to 'main': AccessDenied
2145 \\
2146 },
2147 // git
2148 .{ .files = &.{ "build.zig", "build.zig.zon" } },
2149 .{ .files = &.{ "build.zig", "build.zig.zon", "main" } },
2150 .{ .err_msg =
2151 \\error: unable to unpack packfile
2152 \\ note: unable to create file 'main': PathAlreadyExists
2153 \\ note: unable to create file 'subdir/main': PathAlreadyExists
2154 \\
2155 },
2156 .{ .err_msg =
2157 \\error: unable to unpack packfile
2158 \\ note: unable to create symlink from 'link' to 'main': AccessDenied
2159 \\ note: unable to create symlink from 'subdir/link' to 'main': AccessDenied
2160 \\
2161 },
2162 };
2163
2164 var expected_no: usize = 0;
2165 inline for (urls) |url_fmt| {
2166 var tmp = try fat32TmpDir();
2167 defer tmp.cleanup();
2168
2169 for (branches) |branch| {
2170 defer expected_no += 1;
2171 const url = try std.fmt.bufPrint(&buf, url_fmt, .{branch});
2172 // std.debug.print("fetching url: {s}\n", .{url});
2173
2174 var fb: TestFetchBuilder = undefined;
2175 var fetch = try fb.build(gpa, tmp.dir, url);
2176 defer fb.deinit();
2177
2178 const ex = expected[expected_no];
2179 if (ex.err_msg.len > 0) {
2180 try std.testing.expectError(error.FetchFailed, fetch.run());
2181 try fb.expectFetchErrors(0, ex.err_msg);
2182 } else {
2183 try fetch.run();
2184 try fb.expectPackageFiles(ex.files);
2185 }
2186 }
2187 }
2188}
2189
2190// Using logic from std.testing.tmpDir() to make temporary directory at specific
2191// location.
2192//
2193// This assumes FAT32 file system in /tmp/fat32.mnt folder,
2194// created with something like this:
2195// $ cd /tmp && fallocate -l 1M fat32.fs && mkfs.fat -F32 fat32.fs && mkdir fat32.mnt && sudo mount -o rw,umask=0000 fat32.fs fat32.mnt
2196//
2197// To remove that folder:
2198// $ cd /tmp && sudo umount fat32.mnt && rm -rf fat32.mnt fat32.fs
2199//
2200pub fn fat32TmpDir() !std.testing.TmpDir {
2201 const fat32fs_path = "/tmp/fat32.mnt/";
2202
2203 const random_bytes_count = 12;
2204 var random_bytes: [random_bytes_count]u8 = undefined;
2205 std.crypto.random.bytes(&random_bytes);
2206 var sub_path: [std.fs.base64_encoder.calcSize(random_bytes_count)]u8 = undefined;
2207 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
2208
2209 const parent_dir = std.fs.openDirAbsolute(fat32fs_path, .{}) catch |err| switch (err) {
2210 error.FileNotFound => return error.SkipZigTest,
2211 else => return err,
2212 };
2213 const dir = parent_dir.makeOpenPath(&sub_path, .{}) catch
2214 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
2215
2216 return .{
2217 .dir = dir,
2218 .parent_dir = parent_dir,
2219 .sub_path = sub_path,
2220 };
2221}