From b422e4a202f3b4d6e33c1433c3d27152dc5bc925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Thu, 4 Apr 2024 21:28:28 +0200 Subject: [PATCH] fetch: save test cases Prepare test cases, store them in Fetch/testdata. They cover changes in this PR as well from previous one #19111. --- src/Package/Fetch.zig | 162 ++++++++++-------- .../Fetch/testdata/duplicate_paths.tar.gz | Bin 0 -> 3230 bytes .../testdata/duplicate_paths_excluded.tar.gz | Bin 0 -> 3237 bytes src/Package/Fetch/testdata/no_root.tar.gz | Bin 0 -> 3172 bytes 4 files changed, 88 insertions(+), 74 deletions(-) create mode 100644 src/Package/Fetch/testdata/duplicate_paths.tar.gz create mode 100644 src/Package/Fetch/testdata/duplicate_paths_excluded.tar.gz create mode 100644 src/Package/Fetch/testdata/no_root.tar.gz diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index a841a754f3..a393c7835e 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1890,13 +1890,27 @@ const UnpackResult = struct { } }; -test "tarball with duplicate file names" { +test "tarball with duplicate paths" { + // This tarball has duplicate path 'dir1/file1' to simulate case sensitve + // file system on any file sytstem. + // + // duplicate_paths/ + // duplicate_paths/dir1/ + // duplicate_paths/dir1/file1 + // duplicate_paths/dir1/file1 + // duplicate_paths/build.zig.zon + // duplicate_paths/src/ + // duplicate_paths/src/main.zig + // duplicate_paths/src/root.zig + // duplicate_paths/build.zig + // + const gpa = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tarball_name = "package.tar"; - try createTestTarball(tmp.dir, tarball_name, false); + const tarball_name = "duplicate_paths.tar.gz"; + try saveEmbedFile(tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); @@ -1906,32 +1920,87 @@ test "tarball with duplicate file names" { defer fb.deinit(); try std.testing.expectError(error.FetchFailed, fetch.run()); - try fb.expectFetchErrors(2, + try fb.expectFetchErrors(1, \\error: unable to unpack tarball - \\ note: unable to create file 'dir/file': PathAlreadyExists \\ note: unable to create file 'dir1/file1': PathAlreadyExists \\ ); } -test "tarball with error paths excluded" { +test "tarball with excluded duplicate paths" { + // Same as previous tarball but has build.zig.zon wich excludes 'dir1'. + // + // .paths = .{ + // "build.zig", + // "build.zig.zon", + // "src", + // } + // + const gpa = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tarball_name = "package.tar"; - try createTestTarball(tmp.dir, tarball_name, true); + const tarball_name = "duplicate_paths_excluded.tar.gz"; + try saveEmbedFile(tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); // Run tarball fetch, should succeed var fb: TestFetchBuilder = undefined; - var fetch = try fb.build(std.testing.allocator, tmp.dir, tarball_path); + var fetch = try fb.build(gpa, tmp.dir, tarball_path); defer fb.deinit(); try fetch.run(); const hex_digest = Package.Manifest.hexDigest(fetch.actual_hash); - try std.testing.expectEqualStrings("122022afac878639d5ea6fcca14a123e21fd0395c1f2ef2c89017fa71390f73024af", &hex_digest); + try std.testing.expectEqualStrings( + "12200bafe035cbb453dd717741b66e9f9d1e6c674069d06121dafa1b2e62eb6b22da", + &hex_digest, + ); + + const expected_files: []const []const u8 = &.{ + "build.zig", + "build.zig.zon", + "src/main.zig", + "src/root.zig", + }; + try fb.expectPackageFiles(expected_files); +} + +test "tarball without root folder" { + // Tarball with root folder. Manifest excludes dir1 and dir2. + // + // build.zig + // build.zig.zon + // dir1/ + // dir1/file2 + // dir1/file1 + // dir2/ + // dir2/file2 + // src/ + // src/main.zig + // + + const gpa = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const tarball_name = "no_root.tar.gz"; + try saveEmbedFile(tarball_name, tmp.dir); + const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); + defer gpa.free(tarball_path); + + // Run tarball fetch, should succeed + var fb: TestFetchBuilder = undefined; + var fetch = try fb.build(gpa, tmp.dir, tarball_path); + defer fb.deinit(); + try fetch.run(); + + const hex_digest = Package.Manifest.hexDigest(fetch.actual_hash); + try std.testing.expectEqualStrings( + "12209f939bfdcb8b501a61bb4a43124dfa1b2848adc60eec1e4624c560357562b793", + &hex_digest, + ); const expected_files: []const []const u8 = &.{ "build.zig", @@ -1941,6 +2010,14 @@ test "tarball with error paths excluded" { try fb.expectPackageFiles(expected_files); } +fn saveEmbedFile(comptime tarball_name: []const u8, dir: fs.Dir) !void { + //const tarball_name = "duplicate_paths_excluded.tar.gz"; + const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name); + var tmp_file = try dir.createFile(tarball_name, .{}); + defer tmp_file.close(); + try tmp_file.writeAll(tarball_content); +} + // Builds Fetch with required dependencies, clears dependencies on deinit(). const TestFetchBuilder = struct { thread_pool: ThreadPool, @@ -1981,7 +2058,7 @@ const TestFetchBuilder = struct { .hash_tok = 0, .name_tok = 0, .lazy_status = .eager, - .parent_package_root = Cache.Path{ .root_dir = undefined }, + .parent_package_root = Cache.Path{ .root_dir = Cache.Directory{ .handle = cache_dir, .path = null } }, .parent_manifest_ast = null, .prog_node = self.progress.start("Fetch", 0), .job_queue = &self.job_queue, @@ -2061,66 +2138,3 @@ const TestFetchBuilder = struct { try std.testing.expectEqualStrings(msg, al.items); } }; - -// Creates tarball with duplicate files names. Simulating case collisions on -// case insensitive file system without use of that kind of the file system. -// Manifest will exclude those files, so adding manifest should remove duplicate -// files problem. -fn createTestTarball(dir: fs.Dir, tarball_name: []const u8, with_manifest: bool) !void { - const file = try dir.createFile(tarball_name, .{}); - defer file.close(); - - const TarHeader = std.tar.output.Header; - const prefix = tarball_name; - - // add root directory - { - var hdr = TarHeader.init(); - hdr.typeflag = .directory; - try hdr.setPath(prefix, ""); - try hdr.updateChecksum(); - try file.writeAll(std.mem.asBytes(&hdr)); - } - - // add files - const files: []const []const u8 = &.{ - "build.zig", - "src/main.zig", - // duplicate file paths - "dir/file", - "dir1/file1", - "dir/file", - "dir1/file1", - }; - - for (files) |path| { - var hdr = TarHeader.init(); - hdr.typeflag = .regular; - try hdr.setPath(prefix, path); - try hdr.updateChecksum(); - try file.writeAll(std.mem.asBytes(&hdr)); - } - - // add manifest - if (with_manifest) { - const build_zig_zon = - \\ .{ - \\ .name = "fetch", - \\ .version = "0.0.0", - \\ .paths = .{ - \\ "src", - \\ "build.zig", - \\ "build.zig.zon" - \\ }, - \\ } - ; - var hdr = TarHeader.init(); - hdr.typeflag = .regular; - try hdr.setPath(prefix, "build.zig.zon"); - try hdr.setSize(build_zig_zon.len); - try hdr.updateChecksum(); - try file.writeAll(std.mem.asBytes(&hdr)); - try file.writeAll(build_zig_zon); - try file.writeAll(&[_]u8{0} ** (512 - build_zig_zon.len)); - } -} diff --git a/src/Package/Fetch/testdata/duplicate_paths.tar.gz b/src/Package/Fetch/testdata/duplicate_paths.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..118a934c1b03d764e4854f9aeb60ed684467caad GIT binary patch literal 3230 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 Q<99Uv2QI@^xByT905N}g`2YX_ literal 0 HcmV?d00001 diff --git a/src/Package/Fetch/testdata/duplicate_paths_excluded.tar.gz b/src/Package/Fetch/testdata/duplicate_paths_excluded.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..760b37cd40fe43e0907f14943a33ae8341f2f649 GIT binary patch literal 3237 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 Xe)OXs{pd$OenaEGmP{QK08jt`nu}hw literal 0 HcmV?d00001 diff --git a/src/Package/Fetch/testdata/no_root.tar.gz b/src/Package/Fetch/testdata/no_root.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..a3a4baf40fd1d6b2bd101ee59f27f88c6a61f97f GIT binary patch literal 3172 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 KksQ4MPyhh%Hx@Dg literal 0 HcmV?d00001