| ... | ... | @@ -1890,16 +1890,19 @@ const UnpackResult = struct { |
| 1890 | 1890 | } |
| 1891 | 1891 | }; |
| 1892 | 1892 | |
| 1893 | | test "fetch tarball: fail with unable to create file" { |
| 1893 | test "tarball with duplicate file names" { |
| 1894 | const gpa = std.testing.allocator; |
| 1894 | 1895 | var tmp = std.testing.tmpDir(.{}); |
| 1895 | 1896 | defer tmp.cleanup(); |
| 1896 | 1897 | |
| 1897 | 1898 | const tarball_name = "package.tar"; |
| 1898 | 1899 | 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); |
| 1899 | 1902 | |
| 1900 | 1903 | // Run tarball fetch, expect to fail |
| 1901 | 1904 | 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); |
| 1903 | 1906 | defer fb.deinit(); |
| 1904 | 1907 | try std.testing.expectError(error.FetchFailed, fetch.run()); |
| 1905 | 1908 | |
| ... | ... | @@ -1911,16 +1914,19 @@ test "fetch tarball: fail with unable to create file" { |
| 1911 | 1914 | ); |
| 1912 | 1915 | } |
| 1913 | 1916 | |
| 1914 | | test "fetch tarball: error path are excluded" { |
| 1917 | test "tarball with error paths excluded" { |
| 1918 | const gpa = std.testing.allocator; |
| 1915 | 1919 | var tmp = std.testing.tmpDir(.{}); |
| 1916 | 1920 | defer tmp.cleanup(); |
| 1917 | 1921 | |
| 1918 | 1922 | const tarball_name = "package.tar"; |
| 1919 | 1923 | 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); |
| 1920 | 1926 | |
| 1921 | 1927 | // Run tarball fetch, should succeed |
| 1922 | 1928 | 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); |
| 1924 | 1930 | defer fb.deinit(); |
| 1925 | 1931 | try fetch.run(); |
| 1926 | 1932 | |
| ... | ... | @@ -1944,9 +1950,8 @@ const TestFetchBuilder = struct { |
| 1944 | 1950 | job_queue: Fetch.JobQueue, |
| 1945 | 1951 | fetch: Fetch, |
| 1946 | 1952 | |
| 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", .{}); |
| 1950 | 1955 | |
| 1951 | 1956 | try self.thread_pool.init(.{ .allocator = allocator }); |
| 1952 | 1957 | self.http_client = .{ .allocator = allocator }; |
| ... | ... | @@ -1993,7 +1998,6 @@ const TestFetchBuilder = struct { |
| 1993 | 1998 | } |
| 1994 | 1999 | |
| 1995 | 2000 | fn deinit(self: *TestFetchBuilder) void { |
| 1996 | | self.fetch.arena.child_allocator.free(self.fetch.location.path_or_url); |
| 1997 | 2001 | self.fetch.deinit(); |
| 1998 | 2002 | self.job_queue.deinit(); |
| 1999 | 2003 | self.root_prog_node.end(); |
| ... | ... | @@ -2043,8 +2047,9 @@ const TestFetchBuilder = struct { |
| 2043 | 2047 | |
| 2044 | 2048 | const em = errors.getErrorMessage(errors.getMessages()[0]); |
| 2045 | 2049 | 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 | } |
| 2048 | 2053 | var al = std.ArrayList(u8).init(std.testing.allocator); |
| 2049 | 2054 | defer al.deinit(); |
| 2050 | 2055 | try errors.renderToWriter(.{ .ttyconf = .no_color }, al.writer()); |
| ... | ... | @@ -2098,3 +2103,119 @@ fn createTestTarball(dir: fs.Dir, tarball_name: []const u8, with_manifest: bool) |
| 2098 | 2103 | try file.writeAll(&[_]u8{0} ** (512 - build_zig_zon.len)); |
| 2099 | 2104 | } |
| 2100 | 2105 | } |
| 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. |
| 2112 | test "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 | // |
| 2200 | pub 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 | } |